[gnumeric] sstest: move function test and dump code to here from func.c



commit d9c565391771048a9f1408dcb6d3040788fa782d
Author: Morten Welinder <terra gnome org>
Date:   Wed Nov 29 19:42:11 2017 -0500

    sstest: move function test and dump code to here from func.c
    
    That gets it out of libgnumeric.  Hence we won't install it.

 ChangeLog    |    4 +
 src/func.c   |  796 +--------------------------------------------------------
 src/func.h   |    3 +-
 src/sstest.c |  805 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 823 insertions(+), 785 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 3a3f915..e01cdc2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2017-11-29  Morten Welinder  <terra gnome org>
 
+       * src/sstest.c (function_dump_defs): Move from func.c with all
+       support code.
+       (gnm_func_sanity_check): Ditto.
+
        * src/libgnumeric.c (gnm_dump_func_defs): Not needed anymore.
 
        * src/main-application.c (pathetic_qt_workaround): Not needed
diff --git a/src/func.c b/src/func.c
index 8bb88ce..350e617 100644
--- a/src/func.c
+++ b/src/func.c
@@ -35,7 +35,6 @@
 #include <string.h>
 #include <stdlib.h>
 
-#define UNICODE_ELLIPSIS "\xe2\x80\xa6"
 #define F2(func,s) dgettext ((func)->textdomain->str, (s))
 
 static GList       *categories;
@@ -79,56 +78,25 @@ functions_shutdown (void)
        functions_by_localized_name = NULL;
 }
 
-inline void
-gnm_func_load_if_stub (GnmFunc *func)
-{
-       if (func->fn_type == GNM_FUNC_TYPE_STUB)
-               gnm_func_load_stub (func);
-}
-
-static void
-copy_hash_table_to_ptr_array (gpointer key, gpointer value, gpointer array)
-{
-       GnmFunc *fd = value;
-
-       if (fd->name == NULL ||
-           strcmp (fd->name, "perl_adder") == 0 ||
-           strcmp (fd->name, "perl_date") == 0 ||
-           strcmp (fd->name, "perl_sed") == 0 ||
-           strcmp (fd->name, "py_capwords") == 0 ||
-           strcmp (fd->name, "py_printf") == 0 ||
-           strcmp (fd->name, "py_bitand") == 0)
-               return;
-
-       gnm_func_load_if_stub (fd);
-       if (fd->help != NULL)
-               g_ptr_array_add (array, fd);
-}
-
-static int
-func_def_cmp (gconstpointer a, gconstpointer b)
+GPtrArray *
+gnm_func_enumerate (void)
 {
-       GnmFunc const *fda = *(GnmFunc const **)a ;
-       GnmFunc const *fdb = *(GnmFunc const **)b ;
-
-       g_return_val_if_fail (fda->name != NULL, 0);
-       g_return_val_if_fail (fdb->name != NULL, 0);
+       GPtrArray *res = g_ptr_array_new ();
+       GHashTableIter hiter;
+       gpointer value;
 
-       if (fda->fn_group != NULL && fdb->fn_group != NULL) {
-               int res = go_string_cmp (fda->fn_group->display_name,
-                                        fdb->fn_group->display_name);
-               if (res != 0)
-                       return res;
-       }
+       g_hash_table_iter_init (&hiter, functions_by_name);
+       while (g_hash_table_iter_next (&hiter, NULL, &value))
+               g_ptr_array_add (res, value);
 
-       return g_ascii_strcasecmp (fda->name, fdb->name);
+       return res;
 }
 
-static void
-cb_dump_usage (gpointer key, GnmFunc const *fd, FILE *out)
+inline void
+gnm_func_load_if_stub (GnmFunc *func)
 {
-       if (fd->usage_count > 0)
-               fprintf (out, "%d,%s\n", fd->usage_count, fd->name);
+       if (func->fn_type == GNM_FUNC_TYPE_STUB)
+               gnm_func_load_stub (func);
 }
 
 static char *
@@ -145,744 +113,6 @@ split_at_colon (char const *s, char **rest)
        return dup;
 }
 
-static void
-dump_externals (GPtrArray *defs, FILE *out)
-{
-       unsigned int ui;
-
-       fprintf (out, "<!--#set var=\"title\" value=\"Gnumeric Web Documentation\" -->");
-       fprintf (out, "<!--#set var=\"rootdir\" value=\".\" -->");
-       fprintf (out, "<!--#include virtual=\"header-begin.shtml\" -->");
-       fprintf (out, "<link rel=\"stylesheet\" href=\"style/index.css\" type=\"text/css\"/>");
-       fprintf (out, "<!--#include virtual=\"header-end.shtml\" -->");
-       fprintf (out, "<!--#set var=\"wolfram\" value=\"none\" -->");
-       fprintf (out, "<!--#set var=\"wiki\" value=\"none\" -->");
-       fprintf (out, "<!--\n\n-->");
-
-       for (ui = 0; ui < defs->len; ui++) {
-               GnmFunc const *fd = g_ptr_array_index (defs, ui);
-               gboolean any = FALSE;
-               int j;
-
-               for (j = 0; fd->help[j].type != GNM_FUNC_HELP_END; j++) {
-                       const char *s = F2(fd, fd->help[j].text);
-
-                       switch (fd->help[j].type) {
-                       case GNM_FUNC_HELP_EXTREF:
-                               if (!any) {
-                                       any = TRUE;
-                                       fprintf (out, "<!--#if expr=\"${QUERY_STRING} = %s\" -->", fd->name);
-                               }
-
-                               if (strncmp (s, "wolfram:", 8) == 0) {
-                                       fprintf (out, "<!--#set var=\"wolfram\" value=\"%s\" -->", s + 8);
-                               }
-                               if (strncmp (s, "wiki:", 5) == 0) {
-                                       char *lang, *page;
-                                       lang = split_at_colon (s + 5, &page);
-                                       fprintf (out, "<!--#set var=\"wiki_lang\" value=\"%s\" -->", lang);
-                                       fprintf (out, "<!--#set var=\"wiki\" value=\"%s\" -->", page);
-                                       g_free (lang);
-                               }
-                               break;
-                       default:
-                               break;
-                       }
-               }
-
-               if (any)
-                       fprintf (out, "<!--#endif\n\n-->");
-       }
-
-       fprintf (out, "<div class=\"floatflush\">\n");
-       fprintf (out, "<h1>Online Documentation for \"<!--#echo var=\"QUERY_STRING\" -->\"</h1>\n");
-       fprintf (out, "<p>When last checked, these sources provided useful information about\n");
-       fprintf (out, "this function.  However, since the links are not controlled by the\n");
-       fprintf (out, "Gnumeric Team, we cannot guarantee that the links still work.  If\n");
-       fprintf (out, "you find that they do not work, please drop us a line.</p>\n");
-       fprintf (out, "<ul>");
-       fprintf (out, "<!--#if expr=\"${wolfram} != none\"-->");
-       fprintf (out, "<li><a href=\"http://mathworld.wolfram.com/<!--#echo var=\"wolfram\" -->\">Wolfram 
Mathworld\nentry</a>.</li><!--#endif-->");
-       fprintf (out, "<!--#if expr=\"${wiki} != none\"--><li><a href=\"http://<!--#echo var=\"wiki_lang\" 
-->.wikipedia.org/wiki/<!--#echo var=\"wiki\" -->\">Wikipedia\nentry</a>.</li><!--#endif-->");
-       fprintf (out, "<li><a href=\"http://www.google.com/#q=<!--#echo var=\"QUERY_STRING\" -->\">Google 
Search</a>.</li>");
-       fprintf (out, "</ul>");
-       fprintf (out, "</div>\n");
-
-       fprintf (out, "<!--#include virtual=\"footer.shtml\" -->\n");
-}
-
-static void
-csv_quoted_print (FILE *out, const char *s)
-{
-       char quote = '"';
-       fputc (quote, out);
-       while (*s) {
-               if (*s == quote) {
-                       fputc (quote, out);
-                       fputc (quote, out);
-                       s++;
-               } else {
-                       int len = g_utf8_skip[(unsigned char)*s];
-                       fprintf (out, "%-.*s", len, s);
-                       s += len;
-               }
-       }
-       fputc ('"', out);
-}
-
-static void
-dump_samples (GPtrArray *defs, FILE *out)
-{
-       unsigned ui;
-       GnmFuncGroup *last_group = NULL;
-
-       for (ui = 0; ui < defs->len; ui++) {
-               GnmFunc const *fd = g_ptr_array_index (defs, ui);
-               int j;
-               const char *last = NULL;
-               gboolean has_sample = FALSE;
-
-               if (last_group != fd->fn_group) {
-                       last_group = fd->fn_group;
-                       csv_quoted_print (out, last_group->display_name->str);
-                       fputc ('\n', out);
-               }
-
-               for (j = 0; fd->help[j].type != GNM_FUNC_HELP_END; j++) {
-                       const char *s = fd->help[j].text;
-
-                       if (fd->help[j].type != GNM_FUNC_HELP_EXAMPLES)
-                               continue;
-
-                       has_sample = TRUE;
-
-                       /*
-                        * Some of the random numbers functions have duplicate
-                        * samples.  We don't want the duplicates here.
-                        */
-                       if (s[0] != '=' || (last && strcmp (last, s) == 0))
-                               continue;
-
-                       fputc (',', out);
-                       if (!last)
-                               csv_quoted_print (out, fd->name);
-                       last = s;
-
-                       fputc (',', out);
-                       csv_quoted_print (out, s);
-                       fputc ('\n', out);
-               }
-
-               if (!has_sample)
-                       g_printerr ("No samples for %s\n", fd->name);
-       }
-}
-
-/**
- * function_dump_defs :
- * @filename:
- * @dump_type:
- *
- * A generic utility routine to operate on all funtion defs
- * in various ways.  @dump_type will change/extend as needed
- * Right now
- * 0 : www.gnumeric.org's function.shtml page
- * 1 :
- * 2 : (obsolete)
- * 3 : dump function usage count
- * 4 : external refs
- * 5 : all sample expressions
- **/
-void
-function_dump_defs (char const *filename, int dump_type)
-{
-       FILE *output_file;
-       char *up, *catname;
-       unsigned i;
-       GPtrArray *ordered;
-       GnmFuncGroup const *group = NULL;
-
-       g_return_if_fail (filename != NULL);
-
-       if ((output_file = g_fopen (filename, "w")) == NULL){
-               g_printerr (_("Cannot create file %s\n"), filename);
-               exit (1);
-       }
-
-       if (dump_type == 3) {
-               g_hash_table_foreach (functions_by_name,
-                                     (GHFunc) cb_dump_usage,
-                                     output_file);
-               fclose (output_file);
-               return;
-       }
-
-       /* TODO : Use the translated names and split by fn_group. */
-       ordered = g_ptr_array_new ();
-       g_hash_table_foreach (functions_by_name,
-                             copy_hash_table_to_ptr_array, ordered);
-
-       if (ordered->len > 0)
-               qsort (&g_ptr_array_index (ordered, 0),
-                      ordered->len, sizeof (gpointer),
-                      func_def_cmp);
-
-       if (dump_type == 4) {
-               dump_externals (ordered, output_file);
-               g_ptr_array_free (ordered, TRUE);
-               fclose (output_file);
-               return;
-       }
-
-       if (dump_type == 5) {
-               dump_samples (ordered, output_file);
-               g_ptr_array_free (ordered, TRUE);
-               fclose (output_file);
-               return;
-       }
-
-       if (dump_type == 0) {
-               int unique = 0;
-               for (i = 0; i < ordered->len; i++) {
-                       GnmFunc const *fd = g_ptr_array_index (ordered, i);
-                       switch (fd->impl_status) {
-                       case GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC:
-                               unique++;
-                               break;
-                       default: ;
-                       }
-               }
-
-               fprintf (output_file,
-                        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-                        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" 
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\";>\n"
-                        "<html xmlns=\"http://www.w3.org/1999/xhtml\"; xml:lang=\"en\" lang=\"en\">\n"
-                        "<!-- DEFINE current=Home -->\n"
-                        "<!-- MARKER: start-header -->\n"
-                        "<head>\n"
-                        "<title>Gnumeric</title>\n"
-                        "<link rel=\"stylesheet\" href=\"style/style.css\" type=\"text/css\" />\n"
-                        "<link rel=\"icon\" type=\"image/png\" href=\"logo.png\" />\n"
-                        "<style type=\"text/css\"><!--\n"
-                        "  div.functiongroup {\n"
-                        "    margin-top: 1em;\n"
-                        "    margin-bottom: 1em;\n"
-                        "  }\n"
-                        "  table.functiongroup {\n"
-                        "    border-style: solid;\n"
-                        "    border-width: 1px;\n"
-                        "    border-spacing: 0px;\n"
-                        "  }\n"
-                        "  tr.header td {\n"
-                        "    font-weight: bold;\n"
-                        "    font-size: 14pt;\n"
-                        "    border-style: solid;\n"
-                        "    border-width: 1px;\n"
-                        "    text-align: center;\n"
-                        "  }\n"
-                        "  tr.function td {\n"
-                        "    border: solid 1px;\n"
-                        "  }\n"
-                        "  td.testing-unknown    { background: #ffffff; }\n"
-                        "  td.testing-nosuite    { background: #ff7662; }\n"
-                        "  td.testing-basic      { background: #fff79d; }\n"
-                        "  td.testing-exhaustive { background: #aef8b5; }\n"
-                        "  td.testing-devel      { background: #ff6c00; }\n"
-                        "  td.imp-exists         { background: #ffffff; }\n"
-                        "  td.imp-no             { background: #ff7662; }\n"
-                        "  td.imp-subset         { background: #fff79d; }\n"
-                        "  td.imp-complete       { background: #aef8b5; }\n"
-                        "  td.imp-superset       { background: #16e49e; }\n"
-                        "  td.imp-subsetext      { background: #59fff2; }\n"
-                        "  td.imp-devel          { background: #ff6c00; }\n"
-                        "  td.imp-gnumeric       { background: #44be18; }\n"
-                        "--></style>\n"
-                        "</head>\n"
-                        "<body>\n"
-                        "<div id=\"wrap\">\n"
-                        "  <a href=\"/\"><div id=\"header\">\n"
-                        "    <h1 id=\"logo-text\"><span>Gnumeric</span></h1>\n"
-                        "    <p id=\"slogan\">Free, Fast, Accurate &mdash; Pick Any Three!</p>\n"
-                        "    <img id=\"logo\" src=\"gnumeric.png\" alt=\"logo\" class=\"float-right\"/>\n"
-                        "    </div></a>\n"
-                        "\n"
-                        "  <div id=\"nav\">\n"
-                        "    <ul>\n"
-                        "      <li id=\"current\"><a href=\"/\">Home</a></li>\n"
-                        "      <li><a href=\"development.html\">Development</a></li>\n"
-                        "      <li><a href=\"contact.html\">Contact</a></li>\n"
-                        "    </ul>\n"
-                        "  </div>\n"
-                        "\n"
-                        "  <div id=\"content-wrap\">\n"
-                        "    <!-- MARKER: start-main -->\n"
-                        "    <div id=\"main\">\n"
-                        "      <div class=\"generalitem\">\n"
-                        "      <h2><span class=\"gnumeric-bullet\"></span>Gnumeric Sheet Functions</h2>\n"
-                        "      <p>Gnumeric currently has %d functions for use in spreadsheets.\n"
-                        "      %d of these are unique to Gnumeric.</p>\n",
-                        ordered->len, unique);
-       }
-
-       for (i = 0; i < ordered->len; i++) {
-               GnmFunc const *fd = g_ptr_array_index (ordered, i);
-               if (dump_type == 1) {
-                       int i;
-                       gboolean first_arg = TRUE;
-                       GString *syntax = g_string_new (NULL);
-                       GString *arg_desc = g_string_new (NULL);
-                       GString *desc = g_string_new (NULL);
-                       GString *odf = g_string_new (NULL);
-                       GString *excel = g_string_new (NULL);
-                       GString *note = g_string_new (NULL);
-                       GString *seealso = g_string_new (NULL);
-                       gint min, max;
-
-                       fprintf (output_file, "@CATEGORY=%s\n",
-                                F2(fd, fd->fn_group->display_name->str));
-                       for (i = 0;
-                            fd->help[i].type != GNM_FUNC_HELP_END;
-                            i++) {
-                               switch (fd->help[i].type) {
-                               case GNM_FUNC_HELP_NAME: {
-                                       char *short_desc;
-                                       char *name = split_at_colon (F2(fd, fd->help[i].text), &short_desc);
-                                       fprintf (output_file,
-                                                "@FUNCTION=%s\n",
-                                                name);
-                                       fprintf (output_file,
-                                                "@SHORTDESC=%s\n",
-                                                short_desc);
-                                       g_string_append (syntax, name);
-                                       g_string_append_c (syntax, '(');
-                                       g_free (name);
-                                       break;
-                               }
-                               case GNM_FUNC_HELP_SEEALSO:
-                                       if (seealso->len > 0)
-                                               g_string_append (seealso, ",");
-                                       g_string_append (seealso, F2(fd, fd->help[i].text));
-                                       break;
-                               case GNM_FUNC_HELP_DESCRIPTION:
-                                       if (desc->len > 0)
-                                               g_string_append (desc, "\n");
-                                       g_string_append (desc, F2(fd, fd->help[i].text));
-                                       break;
-                               case GNM_FUNC_HELP_NOTE:
-                                       if (note->len > 0)
-                                               g_string_append (note, " ");
-                                       g_string_append (note, F2(fd, fd->help[i].text));
-                                       break;
-                               case GNM_FUNC_HELP_ARG: {
-                                       char *argdesc;
-                                       char *name = split_at_colon (F2(fd, fd->help[i].text), &argdesc);
-                                       if (first_arg)
-                                               first_arg = FALSE;
-                                       else
-                                               g_string_append_c (syntax, go_locale_get_arg_sep ());
-                                       g_string_append (syntax, name);
-                                       if (argdesc) {
-                                               g_string_append_printf (arg_desc,
-                                                                       "@{%s}: %s\n",
-                                                                       name,
-                                                                       argdesc);
-                                       }
-                                       g_free (name);
-                                       /* FIXME: Optional args?  */
-                                       break;
-                               }
-                               case GNM_FUNC_HELP_ODF:
-                                       if (odf->len > 0)
-                                               g_string_append (odf, " ");
-                                       g_string_append (odf, F2(fd, fd->help[i].text));
-                                       break;
-                               case GNM_FUNC_HELP_EXCEL:
-                                       if (excel->len > 0)
-                                               g_string_append (excel, " ");
-                                       g_string_append (excel, F2(fd, fd->help[i].text));
-                                       break;
-
-                               case GNM_FUNC_HELP_EXTREF:
-                                       /* FIXME! */
-                               case GNM_FUNC_HELP_EXAMPLES:
-                                       /* FIXME! */
-                               case GNM_FUNC_HELP_END:
-                                       break;
-                               }
-                       }
-
-                       function_def_count_args (fd, &min, &max);
-                       if (max == G_MAXINT)
-                               fprintf (output_file,
-                                        "@SYNTAX=%s," UNICODE_ELLIPSIS ")\n",
-                                        syntax->str);
-                       else
-                               fprintf (output_file, "@SYNTAX=%s)\n",
-                                        syntax->str);
-
-                       if (arg_desc->len > 0)
-                               fprintf (output_file, "@ARGUMENTDESCRIPTION=%s", arg_desc->str);
-                       if (desc->len > 0)
-                               fprintf (output_file, "@DESCRIPTION=%s\n", desc->str);
-                       if (note->len > 0)
-                               fprintf (output_file, "@NOTE=%s\n", note->str);
-                       if (excel->len > 0)
-                               fprintf (output_file, "@EXCEL=%s\n", excel->str);
-                       if (odf->len > 0)
-                               fprintf (output_file, "@ODF=%s\n", odf->str);
-                       if (seealso->len > 0)
-                               fprintf (output_file, "@SEEALSO=%s\n", seealso->str);
-
-                       g_string_free (syntax, TRUE);
-                       g_string_free (arg_desc, TRUE);
-                       g_string_free (desc, TRUE);
-                       g_string_free (odf, TRUE);
-                       g_string_free (excel, TRUE);
-                       g_string_free (note, TRUE);
-                       g_string_free (seealso, TRUE);
-
-                       fputc ('\n', output_file);
-               } else if (dump_type == 0) {
-                       static struct {
-                               char const *name;
-                               char const *klass;
-                       } const testing [] = {
-                               { "Unknown",            "testing-unknown" },
-                               { "No Testsuite",       "testing-nosuite" },
-                               { "Basic",              "testing-basic" },
-                               { "Exhaustive",         "testing-exhaustive" },
-                               { "Under Development",  "testing-devel" }
-                       };
-                       static struct {
-                               char const *name;
-                               char const *klass;
-                       } const implementation [] = {
-                               { "Exists",                     "imp-exists" },
-                               { "Unimplemented",              "imp-no" },
-                               { "Subset",                     "imp-subset" },
-                               { "Complete",                   "imp-complete" },
-                               { "Superset",                   "imp-superset" },
-                               { "Subset with_extensions",     "imp-subsetext" },
-                               { "Under development",          "imp-devel" },
-                               { "Unique to Gnumeric",         "imp-gnumeric" },
-                       };
-                       if (group != fd->fn_group) {
-                               if (group) fprintf (output_file, "</table></div>\n");
-                               group = fd->fn_group;
-                               fprintf (output_file,
-                                        "<h2>%s</h2>\n"
-                                        "<div class=\"functiongroup\"><table class=\"functiongroup\">\n"
-                                        "<tr class=\"header\">"
-                                        "<td>Function</td>"
-                                        "<td>Implementation</td>"
-                                        "<td>Testing</td>"
-                                        "</tr>\n",
-                                        group->display_name->str);
-                       }
-                       up = g_ascii_strup (fd->name, -1);
-                       catname = g_strdup (group->display_name->str);
-                       while (strchr (catname, ' '))
-                               *strchr (catname, ' ') = '_';
-                       fprintf (output_file, "<tr class=\"function\">\n");
-                       fprintf (output_file,
-                                "<td><a href 
=\"https://help.gnome.org/users/gnumeric/stable/gnumeric.html#gnumeric-function-%s\";>%s</a></td>\n",
-                                up, fd->name);
-                       g_free (up);
-                       g_free (catname);
-                       fprintf (output_file,
-                                "<td class=\"%s\"><a href=\"mailto:gnumeric-list gnome org?subject=Re: %s 
implementation\">%s</a></td>\n",
-                                implementation[fd->impl_status].klass,
-                                fd->name,
-                                implementation[fd->impl_status].name);
-                       fprintf (output_file,
-                                "<td class=\"%s\"><a href=\"mailto:gnumeric-list gnome org?subject=Re: %s 
testing\">%s</a></td>\n",
-                                testing[fd->test_status].klass,
-                                fd->name,
-                                testing[fd->test_status].name);
-                       fprintf (output_file,"</tr>\n");
-               }
-       }
-       if (dump_type == 0) {
-               if (group) fprintf (output_file, "</table></div>\n");
-               fprintf (output_file,
-                        "      </div>\n"
-                        "    </div>\n"
-                        "    <!-- MARKER: end-main -->\n"
-                        "    <!-- MARKER: start-sidebar -->\n"
-                        "    <!-- MARKER: end-sidebar -->\n"
-                        "  </div>\n"
-                        "</div>\n"
-                        "</body>\n"
-                        "</html>\n");
-       }
-
-       g_ptr_array_free (ordered, TRUE);
-       fclose (output_file);
-}
-
-/* ------------------------------------------------------------------------- */
-
-static gboolean
-check_help_expression (const char *text, GnmFunc const *fd)
-{
-       GnmConventions const *convs = gnm_conventions_default;
-       GnmParsePos pp;
-       GnmExprTop const *texpr;
-       Workbook *wb;
-       GnmParseError perr;
-
-       /* Create a dummy workbook with no sheets for interesting effects.  */
-       wb = workbook_new ();
-       parse_pos_init (&pp, wb, NULL, 0, 0);
-
-       parse_error_init (&perr);
-
-       texpr = gnm_expr_parse_str (text, &pp,
-                                   GNM_EXPR_PARSE_DEFAULT,
-                                   convs,
-                                   &perr);
-       if (perr.err) {
-               g_printerr ("Error parsing %s: %s\n",
-                           text, perr.err->message);
-       }
-       parse_error_free (&perr);
-       g_object_unref (wb);
-
-       if (!texpr)
-               return TRUE;
-
-       gnm_expr_top_unref (texpr);
-       return FALSE;
-}
-
-static gboolean
-check_argument_refs (const char *text, GnmFunc const *fd)
-{
-       if (fd->fn_type != GNM_FUNC_TYPE_ARGS)
-               return FALSE;
-
-       while (1) {
-               const char *at = strchr (text, '@');
-               char *argname;
-               int i;
-
-               if (!at)
-                       return FALSE;
-               if (at[1] != '{')
-                       return TRUE;
-               text = strchr (at + 2, '}');
-               if (!text)
-                       return FALSE;
-               argname = g_strndup (at + 2, text - at - 2);
-
-               for (i = 0; TRUE; i++) {
-                       char *thisarg = function_def_get_arg_name (fd, i);
-                       gboolean found;
-                       if (!thisarg) {
-                               g_free (argname);
-                               return TRUE;
-                       }
-                       found = strcmp (argname, thisarg) == 0;
-                       g_free (thisarg);
-                       if (found)
-                               break;
-               }
-               g_free (argname);
-       }
-}
-
-
-static int
-gnm_func_sanity_check1 (GnmFunc const *fd)
-{
-       GnmFuncHelp const *h;
-       int counts[(int)GNM_FUNC_HELP_ODF + 1];
-       int res = 0;
-       size_t nlen = strlen (fd->name);
-       GHashTable *allargs;
-
-       allargs = g_hash_table_new_full
-               (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
-
-       memset (counts, 0, sizeof (counts));
-       for (h = fd->help; h->type != GNM_FUNC_HELP_END; h++) {
-               g_assert (h->type <= GNM_FUNC_HELP_ODF);
-               counts[h->type]++;
-
-               if (!g_utf8_validate (h->text, -1, NULL)) {
-                       g_printerr ("%s: Invalid UTF-8 in type %i\n",
-                                   fd->name, h->type);
-                               res = 1;
-                               continue;
-               }
-
-               switch (h->type) {
-               case GNM_FUNC_HELP_NAME:
-                       if (g_ascii_strncasecmp (fd->name, h->text, nlen) ||
-                           h->text[nlen] != ':') {
-                               g_printerr ("%s: Invalid NAME record\n",
-                                           fd->name);
-                               res = 1;
-                       } else if (h->text[nlen + 1] == ' ') {
-                               g_printerr ("%s: Unwanted space in NAME record\n",
-                                           fd->name);
-                               res = 1;
-                       } else if (h->text[strlen (h->text) - 1] == '.') {
-                               g_printerr ("%s: Unwanted period in NAME record\n",
-                                           fd->name);
-                               res = 1;
-                       }
-                       break;
-               case GNM_FUNC_HELP_ARG: {
-                       const char *aend = strchr (h->text, ':');
-                       char *argname;
-
-                       if (aend == NULL || aend == h->text) {
-                               g_printerr ("%s: Invalid ARG record\n",
-                                           fd->name);
-                               res = 1;
-                               break;
-                       }
-
-                       if (aend[1] == ' ') {
-                               g_printerr ("%s: Unwanted space in ARG record\n",
-                                           fd->name);
-                               res = 1;
-                       }
-                       if (aend[1] == '\0') {
-                               g_printerr ("%s: Empty ARG record\n",
-                                           fd->name);
-                               res = 1;
-                       }
-                       if (h->text[strlen (h->text) - 1] == '.') {
-                               g_printerr ("%s: Unwanted period in ARG record\n",
-                                           fd->name);
-                               res = 1;
-                       }
-                       if (check_argument_refs (aend + 1, fd)) {
-                               g_printerr ("%s: Invalid argument reference in argument\n",
-                                           fd->name);
-                               res = 1;
-                       }
-                       argname = g_strndup (h->text, aend - h->text);
-                       if (g_hash_table_lookup (allargs, argname)) {
-                               g_printerr ("%s: Duplicate argument name %s\n",
-                                           fd->name, argname);
-                               res = 1;
-                               g_free (argname);
-                               g_printerr ("%s\n", h->text);
-                       } else
-                               g_hash_table_insert (allargs, argname, argname);
-                       break;
-               }
-               case GNM_FUNC_HELP_DESCRIPTION: {
-                       const char *p;
-
-                       if (check_argument_refs (h->text, fd)) {
-                               g_printerr ("%s: Invalid argument reference in description\n",
-                                           fd->name);
-                               res = 1;
-                       }
-
-                       p = h->text;
-                       while (g_ascii_isupper (*p) ||
-                              (p != h->text && (*p == '_' ||
-                                                *p == '.' ||
-                                                g_ascii_isdigit (*p))))
-                               p++;
-                       if (*p == ' ' &&
-                           p - h->text >= 2 &&
-                           strncmp (h->text, "CP1252", 6) != 0) {
-                               if (g_ascii_strncasecmp (h->text, fd->name, nlen)) {
-                                       g_printerr ("%s: Wrong function name in description\n",
-                                                   fd->name);
-                                       res = 1;
-                               }
-                       }
-                       break;
-               }
-
-               case GNM_FUNC_HELP_EXAMPLES:
-                       if (h->text[0] == '=') {
-                               if (check_help_expression (h->text + 1, fd)) {
-                                       g_printerr ("%s: Invalid EXAMPLES record\n",
-                                                   fd->name);
-                                       res = 1;
-                               }
-                       }
-                       break;
-               default:
-                       ; /* Nothing */
-               }
-       }
-
-       g_hash_table_destroy (allargs);
-
-       if (fd->fn_type == GNM_FUNC_TYPE_ARGS) {
-               int n = counts[GNM_FUNC_HELP_ARG];
-               if (n != fd->fn.args.max_args) {
-                       g_printerr ("%s: Help for %d args, but takes %d-%d\n",
-                                   fd->name, n,
-                                   fd->fn.args.min_args, fd->fn.args.max_args);
-                       res = 1;
-               }
-       }
-
-#if 0
-       if (counts[GNM_FUNC_HELP_DESCRIPTION] != 1) {
-               g_printerr ("%s: Help has %d descriptions.\n",
-                           fd->name, counts[GNM_FUNC_HELP_DESCRIPTION]);
-               res = 1;
-       }
-#endif
-
-       if (counts[GNM_FUNC_HELP_NAME] != 1) {
-               g_printerr ("%s: Help has %d NAME records.\n",
-                           fd->name, counts[GNM_FUNC_HELP_NAME]);
-               res = 1;
-       }
-
-       if (counts[GNM_FUNC_HELP_EXCEL] > 1) {
-               g_printerr ("%s: Help has %d Excel notes.\n",
-                           fd->name, counts[GNM_FUNC_HELP_EXCEL]);
-               res = 1;
-       }
-
-       if (counts[GNM_FUNC_HELP_ODF] > 1) {
-               g_printerr ("%s: Help has %d ODF notes.\n",
-                           fd->name, counts[GNM_FUNC_HELP_ODF]);
-               res = 1;
-       }
-
-       return res;
-}
-
-int
-gnm_func_sanity_check (void)
-{
-       int res = 0;
-       GPtrArray *ordered;
-       unsigned ui;
-
-       ordered = g_ptr_array_new ();
-       g_hash_table_foreach (functions_by_name,
-                             copy_hash_table_to_ptr_array, ordered);
-       if (ordered->len > 0)
-               qsort (&g_ptr_array_index (ordered, 0),
-                      ordered->len, sizeof (gpointer),
-                      func_def_cmp);
-
-       for (ui = 0; ui < ordered->len; ui++) {
-               GnmFunc const *fd = g_ptr_array_index (ordered, ui);
-               if (gnm_func_sanity_check1 (fd))
-                       res = 1;
-       }
-
-       g_ptr_array_free (ordered, TRUE);
-
-       return res;
-}
-
 /* ------------------------------------------------------------------------- */
 
 static void
diff --git a/src/func.h b/src/func.h
index 3ec536e..a6bcd56 100644
--- a/src/func.h
+++ b/src/func.h
@@ -11,8 +11,7 @@ G_BEGIN_DECLS
 void functions_init     (void);
 void functions_shutdown (void);
 
-void function_dump_defs (char const *filename, int dump_type);
-int gnm_func_sanity_check (void);
+GPtrArray *gnm_func_enumerate (void);
 
 /******************************************************************************/
 /* Function group support */
diff --git a/src/sstest.c b/src/sstest.c
index e85f137..33ba43a 100644
--- a/src/sstest.c
+++ b/src/sstest.c
@@ -30,6 +30,7 @@
 
 #include <gsf/gsf-input-stdio.h>
 #include <gsf/gsf-input-textline.h>
+#include <glib/gstdio.h>
 #include <glib/gi18n.h>
 #include <string.h>
 #include <errno.h>
@@ -89,6 +90,553 @@ static GOptionEntry const sstest_options [] = {
 
 /* ------------------------------------------------------------------------- */
 
+#define UNICODE_ELLIPSIS "\xe2\x80\xa6"
+#define F2(func,s) dgettext ((func)->textdomain->str, (s))
+
+static char *
+split_at_colon (char const *s, char **rest)
+{
+       char *dup = g_strdup (s);
+       char *colon = strchr (dup, ':');
+       if (colon) {
+               *colon = 0;
+               if (rest) *rest = colon + 1;
+       } else {
+               if (rest) *rest = NULL;
+       }
+       return dup;
+}
+
+
+static void
+dump_externals (GPtrArray *defs, FILE *out)
+{
+       unsigned int ui;
+
+       fprintf (out, "<!--#set var=\"title\" value=\"Gnumeric Web Documentation\" -->");
+       fprintf (out, "<!--#set var=\"rootdir\" value=\".\" -->");
+       fprintf (out, "<!--#include virtual=\"header-begin.shtml\" -->");
+       fprintf (out, "<link rel=\"stylesheet\" href=\"style/index.css\" type=\"text/css\"/>");
+       fprintf (out, "<!--#include virtual=\"header-end.shtml\" -->");
+       fprintf (out, "<!--#set var=\"wolfram\" value=\"none\" -->");
+       fprintf (out, "<!--#set var=\"wiki\" value=\"none\" -->");
+       fprintf (out, "<!--\n\n-->");
+
+       for (ui = 0; ui < defs->len; ui++) {
+               GnmFunc const *fd = g_ptr_array_index (defs, ui);
+               gboolean any = FALSE;
+               int j;
+
+               for (j = 0; fd->help[j].type != GNM_FUNC_HELP_END; j++) {
+                       const char *s = F2(fd, fd->help[j].text);
+
+                       switch (fd->help[j].type) {
+                       case GNM_FUNC_HELP_EXTREF:
+                               if (!any) {
+                                       any = TRUE;
+                                       fprintf (out, "<!--#if expr=\"${QUERY_STRING} = %s\" -->", fd->name);
+                               }
+
+                               if (strncmp (s, "wolfram:", 8) == 0) {
+                                       fprintf (out, "<!--#set var=\"wolfram\" value=\"%s\" -->", s + 8);
+                               }
+                               if (strncmp (s, "wiki:", 5) == 0) {
+                                       char *lang, *page;
+                                       lang = split_at_colon (s + 5, &page);
+                                       fprintf (out, "<!--#set var=\"wiki_lang\" value=\"%s\" -->", lang);
+                                       fprintf (out, "<!--#set var=\"wiki\" value=\"%s\" -->", page);
+                                       g_free (lang);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               if (any)
+                       fprintf (out, "<!--#endif\n\n-->");
+       }
+
+       fprintf (out, "<div class=\"floatflush\">\n");
+       fprintf (out, "<h1>Online Documentation for \"<!--#echo var=\"QUERY_STRING\" -->\"</h1>\n");
+       fprintf (out, "<p>When last checked, these sources provided useful information about\n");
+       fprintf (out, "this function.  However, since the links are not controlled by the\n");
+       fprintf (out, "Gnumeric Team, we cannot guarantee that the links still work.  If\n");
+       fprintf (out, "you find that they do not work, please drop us a line.</p>\n");
+       fprintf (out, "<ul>");
+       fprintf (out, "<!--#if expr=\"${wolfram} != none\"-->");
+       fprintf (out, "<li><a href=\"http://mathworld.wolfram.com/<!--#echo var=\"wolfram\" -->\">Wolfram 
Mathworld\nentry</a>.</li><!--#endif-->");
+       fprintf (out, "<!--#if expr=\"${wiki} != none\"--><li><a href=\"http://<!--#echo var=\"wiki_lang\" 
-->.wikipedia.org/wiki/<!--#echo var=\"wiki\" -->\">Wikipedia\nentry</a>.</li><!--#endif-->");
+       fprintf (out, "<li><a href=\"http://www.google.com/#q=<!--#echo var=\"QUERY_STRING\" -->\">Google 
Search</a>.</li>");
+       fprintf (out, "</ul>");
+       fprintf (out, "</div>\n");
+
+       fprintf (out, "<!--#include virtual=\"footer.shtml\" -->\n");
+}
+
+static void
+csv_quoted_print (FILE *out, const char *s)
+{
+       char quote = '"';
+       fputc (quote, out);
+       while (*s) {
+               if (*s == quote) {
+                       fputc (quote, out);
+                       fputc (quote, out);
+                       s++;
+               } else {
+                       int len = g_utf8_skip[(unsigned char)*s];
+                       fprintf (out, "%-.*s", len, s);
+                       s += len;
+               }
+       }
+       fputc ('"', out);
+}
+
+static void
+dump_samples (GPtrArray *defs, FILE *out)
+{
+       unsigned ui;
+       GnmFuncGroup *last_group = NULL;
+
+       for (ui = 0; ui < defs->len; ui++) {
+               GnmFunc const *fd = g_ptr_array_index (defs, ui);
+               int j;
+               const char *last = NULL;
+               gboolean has_sample = FALSE;
+
+               if (last_group != fd->fn_group) {
+                       last_group = fd->fn_group;
+                       csv_quoted_print (out, last_group->display_name->str);
+                       fputc ('\n', out);
+               }
+
+               for (j = 0; fd->help[j].type != GNM_FUNC_HELP_END; j++) {
+                       const char *s = fd->help[j].text;
+
+                       if (fd->help[j].type != GNM_FUNC_HELP_EXAMPLES)
+                               continue;
+
+                       has_sample = TRUE;
+
+                       /*
+                        * Some of the random numbers functions have duplicate
+                        * samples.  We don't want the duplicates here.
+                        */
+                       if (s[0] != '=' || (last && strcmp (last, s) == 0))
+                               continue;
+
+                       fputc (',', out);
+                       if (!last)
+                               csv_quoted_print (out, fd->name);
+                       last = s;
+
+                       fputc (',', out);
+                       csv_quoted_print (out, s);
+                       fputc ('\n', out);
+               }
+
+               if (!has_sample)
+                       g_printerr ("No samples for %s\n", fd->name);
+       }
+}
+
+static void
+cb_dump_usage (GnmFunc const *fd, FILE *out)
+{
+       if (fd->usage_count > 0)
+               fprintf (out, "%d,%s\n", fd->usage_count, fd->name);
+}
+
+
+
+static int
+func_def_cmp (gconstpointer a, gconstpointer b)
+{
+       GnmFunc const *fda = *(GnmFunc const **)a ;
+       GnmFunc const *fdb = *(GnmFunc const **)b ;
+
+       g_return_val_if_fail (fda->name != NULL, 0);
+       g_return_val_if_fail (fdb->name != NULL, 0);
+
+       if (fda->fn_group != NULL && fdb->fn_group != NULL) {
+               int res = go_string_cmp (fda->fn_group->display_name,
+                                        fdb->fn_group->display_name);
+               if (res != 0)
+                       return res;
+       }
+
+       return g_ascii_strcasecmp (fda->name, fdb->name);
+}
+
+static GPtrArray *
+enumerate_functions (gboolean filter)
+{
+       GPtrArray *res = gnm_func_enumerate ();
+
+       if (filter) {
+               unsigned ui;
+               for (ui = 0; ui < res->len; ui++) {
+                       GnmFunc *fd = g_ptr_array_index (res, ui);
+
+                       if (fd->name == NULL ||
+                           strcmp (fd->name, "perl_adder") == 0 ||
+                           strcmp (fd->name, "perl_date") == 0 ||
+                           strcmp (fd->name, "perl_sed") == 0 ||
+                           strcmp (fd->name, "py_capwords") == 0 ||
+                           strcmp (fd->name, "py_printf") == 0 ||
+                           strcmp (fd->name, "py_bitand") == 0) {
+                               g_ptr_array_remove_index_fast (res, ui);
+                               ui--;
+                       }
+               }
+       }
+
+       if (res->len > 0)
+               qsort (&g_ptr_array_index (res, 0),
+                      res->len, sizeof (gpointer),
+                      func_def_cmp);
+
+       return res;
+}
+
+/**
+ * function_dump_defs :
+ * @filename:
+ * @dump_type:
+ *
+ * A generic utility routine to operate on all funtion defs
+ * in various ways.  @dump_type will change/extend as needed
+ * Right now
+ * 0 : www.gnumeric.org's function.shtml page
+ * 1 :
+ * 2 : (obsolete)
+ * 3 : dump function usage count
+ * 4 : external refs
+ * 5 : all sample expressions
+ **/
+static void
+function_dump_defs (char const *filename, int dump_type)
+{
+       FILE *output_file;
+       char *up, *catname;
+       unsigned i;
+       GPtrArray *ordered;
+       GnmFuncGroup const *group = NULL;
+
+       g_return_if_fail (filename != NULL);
+
+       if ((output_file = g_fopen (filename, "w")) == NULL){
+               g_printerr (_("Cannot create file %s\n"), filename);
+               exit (1);
+       }
+
+       if (dump_type == 3) {
+               GPtrArray *funcs = enumerate_functions (FALSE);
+               g_ptr_array_foreach (funcs, (GFunc)cb_dump_usage, output_file);
+               g_ptr_array_free (funcs, TRUE);
+               fclose (output_file);
+               return;
+       }
+
+       /* TODO : Use the translated names and split by fn_group. */
+       ordered = enumerate_functions (TRUE);
+
+       if (dump_type == 4) {
+               dump_externals (ordered, output_file);
+               g_ptr_array_free (ordered, TRUE);
+               fclose (output_file);
+               return;
+       }
+
+       if (dump_type == 5) {
+               dump_samples (ordered, output_file);
+               g_ptr_array_free (ordered, TRUE);
+               fclose (output_file);
+               return;
+       }
+
+       if (dump_type == 0) {
+               int unique = 0;
+               for (i = 0; i < ordered->len; i++) {
+                       GnmFunc const *fd = g_ptr_array_index (ordered, i);
+                       switch (fd->impl_status) {
+                       case GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC:
+                               unique++;
+                               break;
+                       default: ;
+                       }
+               }
+
+               fprintf (output_file,
+                        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+                        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" 
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\";>\n"
+                        "<html xmlns=\"http://www.w3.org/1999/xhtml\"; xml:lang=\"en\" lang=\"en\">\n"
+                        "<!-- DEFINE current=Home -->\n"
+                        "<!-- MARKER: start-header -->\n"
+                        "<head>\n"
+                        "<title>Gnumeric</title>\n"
+                        "<link rel=\"stylesheet\" href=\"style/style.css\" type=\"text/css\" />\n"
+                        "<link rel=\"icon\" type=\"image/png\" href=\"logo.png\" />\n"
+                        "<style type=\"text/css\"><!--\n"
+                        "  div.functiongroup {\n"
+                        "    margin-top: 1em;\n"
+                        "    margin-bottom: 1em;\n"
+                        "  }\n"
+                        "  table.functiongroup {\n"
+                        "    border-style: solid;\n"
+                        "    border-width: 1px;\n"
+                        "    border-spacing: 0px;\n"
+                        "  }\n"
+                        "  tr.header td {\n"
+                        "    font-weight: bold;\n"
+                        "    font-size: 14pt;\n"
+                        "    border-style: solid;\n"
+                        "    border-width: 1px;\n"
+                        "    text-align: center;\n"
+                        "  }\n"
+                        "  tr.function td {\n"
+                        "    border: solid 1px;\n"
+                        "  }\n"
+                        "  td.testing-unknown    { background: #ffffff; }\n"
+                        "  td.testing-nosuite    { background: #ff7662; }\n"
+                        "  td.testing-basic      { background: #fff79d; }\n"
+                        "  td.testing-exhaustive { background: #aef8b5; }\n"
+                        "  td.testing-devel      { background: #ff6c00; }\n"
+                        "  td.imp-exists         { background: #ffffff; }\n"
+                        "  td.imp-no             { background: #ff7662; }\n"
+                        "  td.imp-subset         { background: #fff79d; }\n"
+                        "  td.imp-complete       { background: #aef8b5; }\n"
+                        "  td.imp-superset       { background: #16e49e; }\n"
+                        "  td.imp-subsetext      { background: #59fff2; }\n"
+                        "  td.imp-devel          { background: #ff6c00; }\n"
+                        "  td.imp-gnumeric       { background: #44be18; }\n"
+                        "--></style>\n"
+                        "</head>\n"
+                        "<body>\n"
+                        "<div id=\"wrap\">\n"
+                        "  <a href=\"/\"><div id=\"header\">\n"
+                        "    <h1 id=\"logo-text\"><span>Gnumeric</span></h1>\n"
+                        "    <p id=\"slogan\">Free, Fast, Accurate &mdash; Pick Any Three!</p>\n"
+                        "    <img id=\"logo\" src=\"gnumeric.png\" alt=\"logo\" class=\"float-right\"/>\n"
+                        "    </div></a>\n"
+                        "\n"
+                        "  <div id=\"nav\">\n"
+                        "    <ul>\n"
+                        "      <li id=\"current\"><a href=\"/\">Home</a></li>\n"
+                        "      <li><a href=\"development.html\">Development</a></li>\n"
+                        "      <li><a href=\"contact.html\">Contact</a></li>\n"
+                        "    </ul>\n"
+                        "  </div>\n"
+                        "\n"
+                        "  <div id=\"content-wrap\">\n"
+                        "    <!-- MARKER: start-main -->\n"
+                        "    <div id=\"main\">\n"
+                        "      <div class=\"generalitem\">\n"
+                        "      <h2><span class=\"gnumeric-bullet\"></span>Gnumeric Sheet Functions</h2>\n"
+                        "      <p>Gnumeric currently has %d functions for use in spreadsheets.\n"
+                        "      %d of these are unique to Gnumeric.</p>\n",
+                        ordered->len, unique);
+       }
+
+       for (i = 0; i < ordered->len; i++) {
+               GnmFunc const *fd = g_ptr_array_index (ordered, i);
+               if (dump_type == 1) {
+                       int i;
+                       gboolean first_arg = TRUE;
+                       GString *syntax = g_string_new (NULL);
+                       GString *arg_desc = g_string_new (NULL);
+                       GString *desc = g_string_new (NULL);
+                       GString *odf = g_string_new (NULL);
+                       GString *excel = g_string_new (NULL);
+                       GString *note = g_string_new (NULL);
+                       GString *seealso = g_string_new (NULL);
+                       gint min, max;
+
+                       fprintf (output_file, "@CATEGORY=%s\n",
+                                F2(fd, fd->fn_group->display_name->str));
+                       for (i = 0;
+                            fd->help[i].type != GNM_FUNC_HELP_END;
+                            i++) {
+                               switch (fd->help[i].type) {
+                               case GNM_FUNC_HELP_NAME: {
+                                       char *short_desc;
+                                       char *name = split_at_colon (F2(fd, fd->help[i].text), &short_desc);
+                                       fprintf (output_file,
+                                                "@FUNCTION=%s\n",
+                                                name);
+                                       fprintf (output_file,
+                                                "@SHORTDESC=%s\n",
+                                                short_desc);
+                                       g_string_append (syntax, name);
+                                       g_string_append_c (syntax, '(');
+                                       g_free (name);
+                                       break;
+                               }
+                               case GNM_FUNC_HELP_SEEALSO:
+                                       if (seealso->len > 0)
+                                               g_string_append (seealso, ",");
+                                       g_string_append (seealso, F2(fd, fd->help[i].text));
+                                       break;
+                               case GNM_FUNC_HELP_DESCRIPTION:
+                                       if (desc->len > 0)
+                                               g_string_append (desc, "\n");
+                                       g_string_append (desc, F2(fd, fd->help[i].text));
+                                       break;
+                               case GNM_FUNC_HELP_NOTE:
+                                       if (note->len > 0)
+                                               g_string_append (note, " ");
+                                       g_string_append (note, F2(fd, fd->help[i].text));
+                                       break;
+                               case GNM_FUNC_HELP_ARG: {
+                                       char *argdesc;
+                                       char *name = split_at_colon (F2(fd, fd->help[i].text), &argdesc);
+                                       if (first_arg)
+                                               first_arg = FALSE;
+                                       else
+                                               g_string_append_c (syntax, go_locale_get_arg_sep ());
+                                       g_string_append (syntax, name);
+                                       if (argdesc) {
+                                               g_string_append_printf (arg_desc,
+                                                                       "@{%s}: %s\n",
+                                                                       name,
+                                                                       argdesc);
+                                       }
+                                       g_free (name);
+                                       /* FIXME: Optional args?  */
+                                       break;
+                               }
+                               case GNM_FUNC_HELP_ODF:
+                                       if (odf->len > 0)
+                                               g_string_append (odf, " ");
+                                       g_string_append (odf, F2(fd, fd->help[i].text));
+                                       break;
+                               case GNM_FUNC_HELP_EXCEL:
+                                       if (excel->len > 0)
+                                               g_string_append (excel, " ");
+                                       g_string_append (excel, F2(fd, fd->help[i].text));
+                                       break;
+
+                               case GNM_FUNC_HELP_EXTREF:
+                                       /* FIXME! */
+                               case GNM_FUNC_HELP_EXAMPLES:
+                                       /* FIXME! */
+                               case GNM_FUNC_HELP_END:
+                                       break;
+                               }
+                       }
+
+                       function_def_count_args (fd, &min, &max);
+                       if (max == G_MAXINT)
+                               fprintf (output_file,
+                                        "@SYNTAX=%s," UNICODE_ELLIPSIS ")\n",
+                                        syntax->str);
+                       else
+                               fprintf (output_file, "@SYNTAX=%s)\n",
+                                        syntax->str);
+
+                       if (arg_desc->len > 0)
+                               fprintf (output_file, "@ARGUMENTDESCRIPTION=%s", arg_desc->str);
+                       if (desc->len > 0)
+                               fprintf (output_file, "@DESCRIPTION=%s\n", desc->str);
+                       if (note->len > 0)
+                               fprintf (output_file, "@NOTE=%s\n", note->str);
+                       if (excel->len > 0)
+                               fprintf (output_file, "@EXCEL=%s\n", excel->str);
+                       if (odf->len > 0)
+                               fprintf (output_file, "@ODF=%s\n", odf->str);
+                       if (seealso->len > 0)
+                               fprintf (output_file, "@SEEALSO=%s\n", seealso->str);
+
+                       g_string_free (syntax, TRUE);
+                       g_string_free (arg_desc, TRUE);
+                       g_string_free (desc, TRUE);
+                       g_string_free (odf, TRUE);
+                       g_string_free (excel, TRUE);
+                       g_string_free (note, TRUE);
+                       g_string_free (seealso, TRUE);
+
+                       fputc ('\n', output_file);
+               } else if (dump_type == 0) {
+                       static struct {
+                               char const *name;
+                               char const *klass;
+                       } const testing [] = {
+                               { "Unknown",            "testing-unknown" },
+                               { "No Testsuite",       "testing-nosuite" },
+                               { "Basic",              "testing-basic" },
+                               { "Exhaustive",         "testing-exhaustive" },
+                               { "Under Development",  "testing-devel" }
+                       };
+                       static struct {
+                               char const *name;
+                               char const *klass;
+                       } const implementation [] = {
+                               { "Exists",                     "imp-exists" },
+                               { "Unimplemented",              "imp-no" },
+                               { "Subset",                     "imp-subset" },
+                               { "Complete",                   "imp-complete" },
+                               { "Superset",                   "imp-superset" },
+                               { "Subset with_extensions",     "imp-subsetext" },
+                               { "Under development",          "imp-devel" },
+                               { "Unique to Gnumeric",         "imp-gnumeric" },
+                       };
+                       if (group != fd->fn_group) {
+                               if (group) fprintf (output_file, "</table></div>\n");
+                               group = fd->fn_group;
+                               fprintf (output_file,
+                                        "<h2>%s</h2>\n"
+                                        "<div class=\"functiongroup\"><table class=\"functiongroup\">\n"
+                                        "<tr class=\"header\">"
+                                        "<td>Function</td>"
+                                        "<td>Implementation</td>"
+                                        "<td>Testing</td>"
+                                        "</tr>\n",
+                                        group->display_name->str);
+                       }
+                       up = g_ascii_strup (fd->name, -1);
+                       catname = g_strdup (group->display_name->str);
+                       while (strchr (catname, ' '))
+                               *strchr (catname, ' ') = '_';
+                       fprintf (output_file, "<tr class=\"function\">\n");
+                       fprintf (output_file,
+                                "<td><a href 
=\"https://help.gnome.org/users/gnumeric/stable/gnumeric.html#gnumeric-function-%s\";>%s</a></td>\n",
+                                up, fd->name);
+                       g_free (up);
+                       g_free (catname);
+                       fprintf (output_file,
+                                "<td class=\"%s\"><a href=\"mailto:gnumeric-list gnome org?subject=Re: %s 
implementation\">%s</a></td>\n",
+                                implementation[fd->impl_status].klass,
+                                fd->name,
+                                implementation[fd->impl_status].name);
+                       fprintf (output_file,
+                                "<td class=\"%s\"><a href=\"mailto:gnumeric-list gnome org?subject=Re: %s 
testing\">%s</a></td>\n",
+                                testing[fd->test_status].klass,
+                                fd->name,
+                                testing[fd->test_status].name);
+                       fprintf (output_file,"</tr>\n");
+               }
+       }
+       if (dump_type == 0) {
+               if (group) fprintf (output_file, "</table></div>\n");
+               fprintf (output_file,
+                        "      </div>\n"
+                        "    </div>\n"
+                        "    <!-- MARKER: end-main -->\n"
+                        "    <!-- MARKER: start-sidebar -->\n"
+                        "    <!-- MARKER: end-sidebar -->\n"
+                        "  </div>\n"
+                        "</div>\n"
+                        "</body>\n"
+                        "</html>\n");
+       }
+
+       g_ptr_array_free (ordered, TRUE);
+       fclose (output_file);
+}
+
+/* ------------------------------------------------------------------------- */
+
 static void
 mark_test_start (const char *name)
 {
@@ -350,6 +898,263 @@ test_insert_delete (void)
 
 /* ------------------------------------------------------------------------- */
 
+/* ------------------------------------------------------------------------- */
+
+static gboolean
+check_help_expression (const char *text, GnmFunc const *fd)
+{
+       GnmConventions const *convs = gnm_conventions_default;
+       GnmParsePos pp;
+       GnmExprTop const *texpr;
+       Workbook *wb;
+       GnmParseError perr;
+
+       /* Create a dummy workbook with no sheets for interesting effects.  */
+       wb = workbook_new ();
+       parse_pos_init (&pp, wb, NULL, 0, 0);
+
+       parse_error_init (&perr);
+
+       texpr = gnm_expr_parse_str (text, &pp,
+                                   GNM_EXPR_PARSE_DEFAULT,
+                                   convs,
+                                   &perr);
+       if (perr.err) {
+               g_printerr ("Error parsing %s: %s\n",
+                           text, perr.err->message);
+       }
+       parse_error_free (&perr);
+       g_object_unref (wb);
+
+       if (!texpr)
+               return TRUE;
+
+       gnm_expr_top_unref (texpr);
+       return FALSE;
+}
+
+static gboolean
+check_argument_refs (const char *text, GnmFunc const *fd)
+{
+       if (fd->fn_type != GNM_FUNC_TYPE_ARGS)
+               return FALSE;
+
+       while (1) {
+               const char *at = strchr (text, '@');
+               char *argname;
+               int i;
+
+               if (!at)
+                       return FALSE;
+               if (at[1] != '{')
+                       return TRUE;
+               text = strchr (at + 2, '}');
+               if (!text)
+                       return FALSE;
+               argname = g_strndup (at + 2, text - at - 2);
+
+               for (i = 0; TRUE; i++) {
+                       char *thisarg = function_def_get_arg_name (fd, i);
+                       gboolean found;
+                       if (!thisarg) {
+                               g_free (argname);
+                               return TRUE;
+                       }
+                       found = strcmp (argname, thisarg) == 0;
+                       g_free (thisarg);
+                       if (found)
+                               break;
+               }
+               g_free (argname);
+       }
+}
+
+
+static int
+gnm_func_sanity_check1 (GnmFunc const *fd)
+{
+       GnmFuncHelp const *h;
+       int counts[(int)GNM_FUNC_HELP_ODF + 1];
+       int res = 0;
+       size_t nlen = strlen (fd->name);
+       GHashTable *allargs;
+
+       allargs = g_hash_table_new_full
+               (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
+
+       memset (counts, 0, sizeof (counts));
+       for (h = fd->help; h->type != GNM_FUNC_HELP_END; h++) {
+               g_assert (h->type <= GNM_FUNC_HELP_ODF);
+               counts[h->type]++;
+
+               if (!g_utf8_validate (h->text, -1, NULL)) {
+                       g_printerr ("%s: Invalid UTF-8 in type %i\n",
+                                   fd->name, h->type);
+                               res = 1;
+                               continue;
+               }
+
+               switch (h->type) {
+               case GNM_FUNC_HELP_NAME:
+                       if (g_ascii_strncasecmp (fd->name, h->text, nlen) ||
+                           h->text[nlen] != ':') {
+                               g_printerr ("%s: Invalid NAME record\n",
+                                           fd->name);
+                               res = 1;
+                       } else if (h->text[nlen + 1] == ' ') {
+                               g_printerr ("%s: Unwanted space in NAME record\n",
+                                           fd->name);
+                               res = 1;
+                       } else if (h->text[strlen (h->text) - 1] == '.') {
+                               g_printerr ("%s: Unwanted period in NAME record\n",
+                                           fd->name);
+                               res = 1;
+                       }
+                       break;
+               case GNM_FUNC_HELP_ARG: {
+                       const char *aend = strchr (h->text, ':');
+                       char *argname;
+
+                       if (aend == NULL || aend == h->text) {
+                               g_printerr ("%s: Invalid ARG record\n",
+                                           fd->name);
+                               res = 1;
+                               break;
+                       }
+
+                       if (aend[1] == ' ') {
+                               g_printerr ("%s: Unwanted space in ARG record\n",
+                                           fd->name);
+                               res = 1;
+                       }
+                       if (aend[1] == '\0') {
+                               g_printerr ("%s: Empty ARG record\n",
+                                           fd->name);
+                               res = 1;
+                       }
+                       if (h->text[strlen (h->text) - 1] == '.') {
+                               g_printerr ("%s: Unwanted period in ARG record\n",
+                                           fd->name);
+                               res = 1;
+                       }
+                       if (check_argument_refs (aend + 1, fd)) {
+                               g_printerr ("%s: Invalid argument reference in argument\n",
+                                           fd->name);
+                               res = 1;
+                       }
+                       argname = g_strndup (h->text, aend - h->text);
+                       if (g_hash_table_lookup (allargs, argname)) {
+                               g_printerr ("%s: Duplicate argument name %s\n",
+                                           fd->name, argname);
+                               res = 1;
+                               g_free (argname);
+                               g_printerr ("%s\n", h->text);
+                       } else
+                               g_hash_table_insert (allargs, argname, argname);
+                       break;
+               }
+               case GNM_FUNC_HELP_DESCRIPTION: {
+                       const char *p;
+
+                       if (check_argument_refs (h->text, fd)) {
+                               g_printerr ("%s: Invalid argument reference in description\n",
+                                           fd->name);
+                               res = 1;
+                       }
+
+                       p = h->text;
+                       while (g_ascii_isupper (*p) ||
+                              (p != h->text && (*p == '_' ||
+                                                *p == '.' ||
+                                                g_ascii_isdigit (*p))))
+                               p++;
+                       if (*p == ' ' &&
+                           p - h->text >= 2 &&
+                           strncmp (h->text, "CP1252", 6) != 0) {
+                               if (g_ascii_strncasecmp (h->text, fd->name, nlen)) {
+                                       g_printerr ("%s: Wrong function name in description\n",
+                                                   fd->name);
+                                       res = 1;
+                               }
+                       }
+                       break;
+               }
+
+               case GNM_FUNC_HELP_EXAMPLES:
+                       if (h->text[0] == '=') {
+                               if (check_help_expression (h->text + 1, fd)) {
+                                       g_printerr ("%s: Invalid EXAMPLES record\n",
+                                                   fd->name);
+                                       res = 1;
+                               }
+                       }
+                       break;
+               default:
+                       ; /* Nothing */
+               }
+       }
+
+       g_hash_table_destroy (allargs);
+
+       if (fd->fn_type == GNM_FUNC_TYPE_ARGS) {
+               int n = counts[GNM_FUNC_HELP_ARG];
+               if (n != fd->fn.args.max_args) {
+                       g_printerr ("%s: Help for %d args, but takes %d-%d\n",
+                                   fd->name, n,
+                                   fd->fn.args.min_args, fd->fn.args.max_args);
+                       res = 1;
+               }
+       }
+
+#if 0
+       if (counts[GNM_FUNC_HELP_DESCRIPTION] != 1) {
+               g_printerr ("%s: Help has %d descriptions.\n",
+                           fd->name, counts[GNM_FUNC_HELP_DESCRIPTION]);
+               res = 1;
+       }
+#endif
+
+       if (counts[GNM_FUNC_HELP_NAME] != 1) {
+               g_printerr ("%s: Help has %d NAME records.\n",
+                           fd->name, counts[GNM_FUNC_HELP_NAME]);
+               res = 1;
+       }
+
+       if (counts[GNM_FUNC_HELP_EXCEL] > 1) {
+               g_printerr ("%s: Help has %d Excel notes.\n",
+                           fd->name, counts[GNM_FUNC_HELP_EXCEL]);
+               res = 1;
+       }
+
+       if (counts[GNM_FUNC_HELP_ODF] > 1) {
+               g_printerr ("%s: Help has %d ODF notes.\n",
+                           fd->name, counts[GNM_FUNC_HELP_ODF]);
+               res = 1;
+       }
+
+       return res;
+}
+
+static int
+gnm_func_sanity_check (void)
+{
+       int res = 0;
+       GPtrArray *ordered;
+       unsigned ui;
+
+       ordered = enumerate_functions (TRUE);
+
+       for (ui = 0; ui < ordered->len; ui++) {
+               GnmFunc const *fd = g_ptr_array_index (ordered, ui);
+               if (gnm_func_sanity_check1 (fd))
+                       res = 1;
+       }
+
+       g_ptr_array_free (ordered, TRUE);
+
+       return res;
+}
+
 static void
 test_func_help (void)
 {



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]