1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-25 04:16:38 +02:00
Files
weechat/src/core/core-doc.c
T
2025-02-01 23:13:18 +01:00

1781 lines
50 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* core-doc.c - documentation generator
*
* Copyright (C) 2023-2025 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <gcrypt.h>
#include <regex.h>
#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;
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 (&regex, "[,@*():&|]+", REG_EXTENDED) != 0)
return NULL;
index_string_escaped = (index_string_escaped + 1) % 32;
free (string_escaped[index_string_escaped]);
string_escaped[index_string_escaped] = string_replace_regex (
message, &regex, "-", '$', NULL, NULL);
regfree (&regex);
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, "\"", "\\\"");
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) ? "\"" : "");
free (desc_escaped);
free (values);
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]));
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);
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];
#if ENABLE_NLS == 1
char *localedir;
#endif /* ENABLE_NLS == 1 */
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`)
*/
#if ENABLE_NLS == 1
localedir = getenv ("WEECHAT_DOCGEN_LOCALEDIR");
if (localedir && localedir[0])
bindtextdomain (PACKAGE, localedir);
#endif /* ENABLE_NLS == 1 */
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++)
{
free (string_escaped[i]);
}
return rc_doc_gen;
}