Create HTML documentation for C API

Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/faa11a1e
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/faa11a1e
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/faa11a1e

Branch: refs/heads/master
Commit: faa11a1ed6eb4a260df9222cca88666649683849
Parents: 4776134
Author: Nick Wellnhofer <[email protected]>
Authored: Sun Nov 9 23:32:16 2014 +0100
Committer: Nick Wellnhofer <[email protected]>
Committed: Wed Dec 24 16:02:04 2014 +0100

----------------------------------------------------------------------
 compiler/c/cfc.c        |   1 +
 compiler/src/CFCC.c     |   9 +
 compiler/src/CFCC.h     |   5 +
 compiler/src/CFCCHtml.c | 891 +++++++++++++++++++++++++++++++++++++++++++
 compiler/src/CFCCHtml.h |  71 ++++
 compiler/src/CFCUtil.c  |   6 +
 compiler/src/CFCUtil.h  |   5 +
 7 files changed, 988 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/c/cfc.c
----------------------------------------------------------------------
diff --git a/compiler/c/cfc.c b/compiler/c/cfc.c
index ae8828f..6460e53 100644
--- a/compiler/c/cfc.c
+++ b/compiler/c/cfc.c
@@ -239,6 +239,7 @@ main(int argc, char **argv) {
     CFCC_write_hostdefs(c_binding);
     if (args.num_source_dirs != 0) {
         CFCC_write_callbacks(c_binding);
+        CFCC_write_html_docs(c_binding);
         CFCC_write_man_pages(c_binding);
     }
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCC.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCC.c b/compiler/src/CFCC.c
index d1ae4dc..adc6013 100644
--- a/compiler/src/CFCC.c
+++ b/compiler/src/CFCC.c
@@ -22,6 +22,7 @@
 #define CFC_NEED_BASE_STRUCT_DEF
 #include "CFCBase.h"
 #include "CFCC.h"
+#include "CFCCHtml.h"
 #include "CFCCMan.h"
 #include "CFCClass.h"
 #include "CFCHierarchy.h"
@@ -32,6 +33,7 @@
 struct CFCC {
     CFCBase base;
     CFCHierarchy *hierarchy;
+    CFCCHtml     *html_gen;
     char         *c_header;
     char         *c_footer;
     char         *man_header;
@@ -60,6 +62,7 @@ CFCC_init(CFCC *self, CFCHierarchy *hierarchy, const char 
*header,
     CFCUTIL_NULL_CHECK(header);
     CFCUTIL_NULL_CHECK(footer);
     self->hierarchy  = (CFCHierarchy*)CFCBase_incref((CFCBase*)hierarchy);
+    self->html_gen   = CFCCHtml_new(hierarchy, header, footer);
     self->c_header   = CFCUtil_make_c_comment(header);
     self->c_footer   = CFCUtil_make_c_comment(footer);
     self->man_header = CFCUtil_make_troff_comment(header);
@@ -70,6 +73,7 @@ CFCC_init(CFCC *self, CFCHierarchy *hierarchy, const char 
*header,
 void
 CFCC_destroy(CFCC *self) {
     CFCBase_decref((CFCBase*)self->hierarchy);
+    CFCBase_decref((CFCBase*)self->html_gen);
     FREEMEM(self->c_header);
     FREEMEM(self->c_footer);
     FREEMEM(self->man_header);
@@ -144,6 +148,11 @@ S_callback_decs(CFCClass *klass) {
 }
 
 void
+CFCC_write_html_docs(CFCC *self) {
+    CFCCHtml_write_html_docs(self->html_gen);
+}
+
+void
 CFCC_write_man_pages(CFCC *self) {
     CFCHierarchy  *hierarchy = self->hierarchy;
     CFCClass     **ordered   = CFCHierarchy_ordered_classes(hierarchy);

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCC.h
----------------------------------------------------------------------
diff --git a/compiler/src/CFCC.h b/compiler/src/CFCC.h
index f4501ec..093b043 100644
--- a/compiler/src/CFCC.h
+++ b/compiler/src/CFCC.h
@@ -56,6 +56,11 @@ CFCC_write_callbacks(CFCC *self);
 void
 CFCC_write_hostdefs(CFCC *self);
 
+/** Write the HTML documentation.
+ */
+void
+CFCC_write_html_docs(CFCC *self);
+
 /** Write all man pages.
  */
 void

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCCHtml.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCCHtml.c b/compiler/src/CFCCHtml.c
new file mode 100644
index 0000000..73b25cf
--- /dev/null
+++ b/compiler/src/CFCCHtml.c
@@ -0,0 +1,891 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmark.h>
+
+#include "charmony.h"
+
+#define CFC_NEED_BASE_STRUCT_DEF
+#include "CFCBase.h"
+#include "CFCCHtml.h"
+#include "CFCC.h"
+#include "CFCClass.h"
+#include "CFCDocuComment.h"
+#include "CFCFunction.h"
+#include "CFCHierarchy.h"
+#include "CFCMethod.h"
+#include "CFCParamList.h"
+#include "CFCParcel.h"
+#include "CFCSymbol.h"
+#include "CFCType.h"
+#include "CFCUtil.h"
+#include "CFCUri.h"
+#include "CFCVariable.h"
+
+#ifndef true
+    #define true 1
+    #define false 0
+#endif
+
+#define UTF8_NDASH "\xE2\x80\x93"
+
+struct CFCCHtml {
+    CFCBase base;
+    CFCHierarchy *hierarchy;
+    char *doc_path;
+    char *header;
+    char *footer;
+};
+
+static const CFCMeta CFCCHTML_META = {
+    "Clownfish::CFC::Binding::C::Html",
+    sizeof(CFCCHtml),
+    (CFCBase_destroy_t)CFCCHtml_destroy
+};
+
+static const char header_template[] =
+    "<!DOCTYPE html>\n"
+    "<html>\n"
+    "<head>\n"
+    "<meta charset=\"utf-8\">\n"
+    "{autogen_header}"
+    "<meta name=\"viewport\" content=\"width=device-width\" />\n"
+    "<title>{title}</title>\n"
+    "<style type=\"text/css\">\n"
+    "body {\n"
+    "    max-width: 48em;\n"
+    "    font: 0.85em/1.4 sans-serif;\n"
+    "}\n"
+    "a {\n"
+    "    color: #23b;\n"
+    "}\n"
+    "table {\n"
+    "    border-collapse: collapse;\n"
+    "}\n"
+    "td {\n"
+    "    padding: 0;\n"
+    "}\n"
+    "td.label {\n"
+    "    padding-right: 2em;\n"
+    "    font-weight: bold;\n"
+    "}\n"
+    "dt {\n"
+    "    font-weight: bold;\n"
+    "}\n"
+    "pre {\n"
+    "    border: 1px solid #ccc;\n"
+    "    padding: 0.2em 0.4em;\n"
+    "    background: #f6f6f6;\n"
+    "    font-size: 0.92em;\n"
+    "}\n"
+    "pre a {\n"
+    "    text-decoration: none;\n"
+    "}\n"
+    "pre, code {\n"
+    "    font-family: \"Consolas\", \"Menlo\", monospace;\n"
+    "}\n"
+    "span.prefix, span.comment {\n"
+    "    color: #888;\n"
+    "}\n"
+    "</style>\n"
+    "</head>\n"
+    "<body>\n";
+
+static const char footer_template[] =
+    "</body>\n"
+    "</html>\n"
+    "{autogen_footer}";
+
+static int
+S_compare_class_name(const void *va, const void *vb);
+
+static char*
+S_index_filename(CFCParcel *parcel);
+
+static char*
+S_html_create_name(CFCClass *klass);
+
+static char*
+S_html_create_synopsis(CFCClass *klass);
+
+static char*
+S_html_create_description(CFCClass *klass);
+
+static char*
+S_html_create_functions(CFCClass *klass);
+
+static char*
+S_html_create_methods(CFCClass *klass);
+
+static char*
+S_html_create_fresh_methods(CFCClass *klass, CFCClass *ancestor);
+
+static char*
+S_html_create_func(CFCClass *klass, CFCFunction *func, const char *prefix,
+                   const char *short_sym);
+
+static char*
+S_html_create_param_list(CFCClass *klass, CFCFunction *func);
+
+static char*
+S_html_create_inheritance(CFCClass *klass);
+
+static char*
+S_md_to_html(CFCClass *klass, const char *md);
+
+static void
+S_convert_uris(CFCClass *klass, cmark_node *node);
+
+static void
+S_convert_uri(CFCClass *klass, cmark_node *link);
+
+static char*
+S_type_to_html(CFCClass *klass, CFCType *type);
+
+CFCCHtml*
+CFCCHtml_new(CFCHierarchy *hierarchy, const char *header, const char *footer) {
+    CFCCHtml *self = (CFCCHtml*)CFCBase_allocate(&CFCCHTML_META);
+    return CFCCHtml_init(self, hierarchy, header, footer);
+}
+
+CFCCHtml*
+CFCCHtml_init(CFCCHtml *self, CFCHierarchy *hierarchy, const char *header,
+              const char *footer) {
+    CFCUTIL_NULL_CHECK(hierarchy);
+    CFCUTIL_NULL_CHECK(header);
+    CFCUTIL_NULL_CHECK(footer);
+
+    self->hierarchy = (CFCHierarchy*)CFCBase_incref((CFCBase*)hierarchy);
+
+    const char *dest = CFCHierarchy_get_dest(hierarchy);
+    self->doc_path
+        = CFCUtil_sprintf("%s" CHY_DIR_SEP "share" CHY_DIR_SEP "doc"
+                          CHY_DIR_SEP "clownfish", dest);
+
+    char *header_comment = CFCUtil_make_html_comment(header);
+    char *footer_comment = CFCUtil_make_html_comment(footer);
+    self->header = CFCUtil_global_replace(header_template, "{autogen_header}",
+                                          header_comment);
+    self->footer = CFCUtil_global_replace(footer_template, "{autogen_footer}",
+                                          footer_comment);
+    FREEMEM(footer_comment);
+    FREEMEM(header_comment);
+
+    return self;
+}
+
+void
+CFCCHtml_destroy(CFCCHtml *self) {
+    CFCBase_decref((CFCBase*)self->hierarchy);
+    FREEMEM(self->doc_path);
+    FREEMEM(self->header);
+    FREEMEM(self->footer);
+    CFCBase_destroy((CFCBase*)self);
+}
+
+void
+CFCCHtml_write_html_docs(CFCCHtml *self) {
+    CFCHierarchy  *hierarchy = self->hierarchy;
+    CFCClass     **ordered   = CFCHierarchy_ordered_classes(hierarchy);
+    CFCParcel    **parcels   = CFCParcel_all_parcels();
+    const char    *doc_path  = self->doc_path;
+
+    size_t num_parcels = 0;
+    for (size_t i = 0; parcels[i] != NULL; i++) {
+        ++num_parcels;
+    }
+
+    size_t num_classes = 0;
+    for (size_t i = 0; ordered[i] != NULL; i++) {
+        ++num_classes;
+    }
+
+    qsort(ordered, num_classes, sizeof(*ordered), S_compare_class_name);
+
+    size_t   max_docs  = num_classes + num_parcels;
+    char   **filenames = (char**)CALLOCATE(max_docs, sizeof(char*));
+    char   **html_docs = (char**)CALLOCATE(max_docs, sizeof(char*));
+    size_t   num_docs  = 0;
+
+    // Generate HTML docs, but don't write.  That way, if there's an error
+    // while generating the pages, we leak memory but don't clutter up the file
+    // system.
+
+    for (size_t i = 0; parcels[i] != NULL; ++i) {
+        CFCParcel *parcel = parcels[i];
+        if (CFCParcel_included(parcel)) { continue; }
+
+        char *html = CFCCHtml_create_index_doc(self, parcel, ordered);
+        if (html != NULL) {
+            filenames[num_docs] = S_index_filename(parcel);
+            html_docs[num_docs] = html;
+            ++num_docs;
+        }
+    }
+
+    for (size_t i = 0; ordered[i] != NULL; i++) {
+        CFCClass *klass = ordered[i];
+        if (CFCClass_included(klass) || !CFCSymbol_public((CFCSymbol*)klass)) {
+            continue;
+        }
+
+        const char *full_struct_sym = CFCClass_full_struct_sym(klass);
+        filenames[num_docs] = CFCUtil_sprintf("%s.html", full_struct_sym);
+        html_docs[num_docs] = CFCCHtml_create_html_doc(self, klass);
+        ++num_docs;
+    }
+
+    if (!CFCUtil_is_dir(doc_path)) {
+        CFCUtil_make_path(doc_path);
+        if (!CFCUtil_is_dir(doc_path)) {
+            CFCUtil_die("Can't make path %s", doc_path);
+        }
+    }
+
+    for (size_t i = 0; i < num_docs; ++i) {
+        char *filename = filenames[i];
+        char *path     = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s", doc_path,
+                                         filename);
+        char *html_doc = html_docs[i];
+        CFCUtil_write_if_changed(path, html_doc, strlen(html_doc));
+        FREEMEM(html_doc);
+        FREEMEM(path);
+        FREEMEM(filename);
+    }
+
+    FREEMEM(html_docs);
+    FREEMEM(filenames);
+    FREEMEM(ordered);
+}
+
+char*
+CFCCHtml_create_index_doc(CFCCHtml *self, CFCParcel *parcel,
+                          CFCClass **classes) {
+    const char *prefix      = CFCParcel_get_prefix(parcel);
+    const char *parcel_name = CFCParcel_get_name(parcel);
+    char *class_list = CFCUtil_strdup("");
+
+    for (size_t i = 0; classes[i] != NULL; i++) {
+        CFCClass *klass = classes[i];
+        if (strcmp(CFCClass_get_prefix(klass), prefix) != 0
+            || !CFCSymbol_public((CFCSymbol*)klass)
+        ) {
+            continue;
+        }
+
+        const char *struct_sym = CFCClass_full_struct_sym(klass);
+        const char *class_name = CFCClass_get_class_name(klass);
+        class_list
+            = CFCUtil_cat(class_list, "<li><a href=\"", struct_sym, ".html\">",
+                          class_name, "</a></li>\n", NULL);
+    }
+
+    if (class_list[0] == '\0') {
+        FREEMEM(class_list);
+        return NULL;
+    }
+
+    char *title
+        = CFCUtil_sprintf("%s " UTF8_NDASH " C API Index", parcel_name);
+    char *header = CFCUtil_global_replace(self->header, "{title}", title);
+
+    const char pattern[] =
+        "%s"
+        "<h1>%s</h1>\n"
+        "<ul>\n"
+        "%s"
+        "</ul>\n"
+        "%s";
+    char *html_doc
+        = CFCUtil_sprintf(pattern, header, title, class_list, self->footer);
+
+    FREEMEM(header);
+    FREEMEM(title);
+    FREEMEM(class_list);
+
+    return html_doc;
+}
+
+char*
+CFCCHtml_create_html_doc(CFCCHtml *self, CFCClass *klass) {
+    CFCParcel  *parcel         = CFCClass_get_parcel(klass);
+    const char *parcel_name    = CFCParcel_get_name(parcel);
+    const char *class_name     = CFCClass_get_class_name(klass);
+    const char *class_nickname = CFCClass_get_nickname(klass);
+    const char *class_var      = CFCClass_full_class_var(klass);
+    const char *struct_sym     = CFCClass_full_struct_sym(klass);
+
+    // Create NAME.
+    char *name = S_html_create_name(klass);
+
+    // Create SYNOPSIS.
+    char *synopsis = S_html_create_synopsis(klass);
+
+    // Create DESCRIPTION.
+    char *description = S_html_create_description(klass);
+
+    // Create CONSTRUCTORS.
+    char *functions_html = S_html_create_functions(klass);
+
+    // Create METHODS, possibly including an ABSTRACT METHODS section.
+    char *methods_html = S_html_create_methods(klass);
+
+    // Build an INHERITANCE section describing class ancestry.
+    char *inheritance = S_html_create_inheritance(klass);
+
+    char *title
+        = CFCUtil_sprintf("%s " UTF8_NDASH " C API Documentation", class_name);
+    char *header = CFCUtil_global_replace(self->header, "{title}", title);
+    char *index_filename = S_index_filename(parcel);
+
+    // Put it all together.
+    const char pattern[] =
+        "%s"
+        "<h1>%s</h1>\n"
+        "<table>\n"
+        "<tr>\n"
+        "<td class=\"label\">parcel</td>\n"
+        "<td><a href=\"%s\">%s</a></td>\n"
+        "</tr>\n"
+        "<tr>\n"
+        "<td class=\"label\">class name</td>\n"
+        "<td>%s</td>\n"
+        "</tr>\n"
+        "<tr>\n"
+        "<td class=\"label\">class nickname</td>\n"
+        "<td>%s</td>\n"
+        "</tr>\n"
+        "<tr>\n"
+        "<td class=\"label\">class variable</td>\n"
+        "<td><code>%s</code></td>\n"
+        "</tr>\n"
+        "<tr>\n"
+        "<td class=\"label\">struct symbol</td>\n"
+        "<td><code>%s</code></td>\n"
+        "</tr>\n"
+        "</table>\n"
+        "%s"
+        "%s"
+        "%s"
+        "%s"
+        "%s"
+        "%s"
+        "%s";
+    char *html_doc
+        = CFCUtil_sprintf(pattern, header, class_name, index_filename,
+                          parcel_name, class_name, class_nickname, class_var,
+                          struct_sym, name, synopsis, description,
+                          functions_html, methods_html, inheritance,
+                          self->footer);
+
+    FREEMEM(index_filename);
+    FREEMEM(header);
+    FREEMEM(title);
+    FREEMEM(name);
+    FREEMEM(synopsis);
+    FREEMEM(description);
+    FREEMEM(functions_html);
+    FREEMEM(methods_html);
+    FREEMEM(inheritance);
+
+    return html_doc;
+}
+
+static int
+S_compare_class_name(const void *va, const void *vb) {
+    const char *a = CFCClass_get_class_name(*(CFCClass**)va);
+    const char *b = CFCClass_get_class_name(*(CFCClass**)vb);
+
+    return strcmp(a, b);
+}
+
+static char*
+S_index_filename(CFCParcel *parcel) {
+    char *nickname = CFCUtil_strdup(CFCParcel_get_nickname(parcel));
+    for (size_t i = 0; nickname[i]; ++i) {
+        nickname[i] = tolower(nickname[i]);
+    }
+    char *filename = CFCUtil_sprintf("%s.html", nickname);
+    FREEMEM(nickname);
+    return filename;
+}
+
+static char*
+S_html_create_name(CFCClass *klass) {
+    const char     *class_name = CFCClass_get_class_name(klass);
+    char           *md         = CFCUtil_strdup(class_name);
+    CFCDocuComment *docucom    = CFCClass_get_docucomment(klass);
+
+    if (docucom) {
+        const char *raw_brief = CFCDocuComment_get_brief(docucom);
+        if (raw_brief && raw_brief[0] != '\0') {
+            md = CFCUtil_cat(md, " " UTF8_NDASH " ", raw_brief, NULL);
+        }
+    }
+
+    char *html = S_md_to_html(klass, md);
+
+    const char *format =
+        "<h2>Name</h2>\n"
+        "%s";
+    char *result = CFCUtil_sprintf(format, html);
+
+    FREEMEM(html);
+    FREEMEM(md);
+    return result;
+}
+
+static char*
+S_html_create_synopsis(CFCClass *klass) {
+    CHY_UNUSED_VAR(klass);
+    return CFCUtil_strdup("");
+}
+
+static char*
+S_html_create_description(CFCClass *klass) {
+    CFCDocuComment *docucom = CFCClass_get_docucomment(klass);
+    char           *desc    = NULL;
+
+    if (docucom) {
+        const char *raw_desc = CFCDocuComment_get_long(docucom);
+        if (raw_desc && raw_desc[0] != '\0') {
+            desc = S_md_to_html(klass, raw_desc);
+        }
+    }
+
+    if (!desc) { return CFCUtil_strdup(""); }
+
+    char *result = CFCUtil_sprintf("<h2>Description</h2>\n%s", desc);
+
+    FREEMEM(desc);
+    return result;
+}
+
+static char*
+S_html_create_functions(CFCClass *klass) {
+    CFCFunction **functions = CFCClass_functions(klass);
+    char         *result    = CFCUtil_strdup("");
+
+    for (int func_num = 0; functions[func_num] != NULL; func_num++) {
+        CFCFunction *func = functions[func_num];
+        if (!CFCFunction_public(func)) { continue; }
+
+        if (result[0] == '\0') {
+            result = CFCUtil_cat(result, "<h2>Functions</h2>\n<dl>\n", NULL);
+        }
+
+        const char *micro_sym = CFCFunction_micro_sym(func);
+        result = CFCUtil_cat(result, "<dt id=\"func_", micro_sym, "\">",
+                             micro_sym, "</dt>\n", NULL);
+
+        CFCParcel  *parcel    = CFCSymbol_get_parcel((CFCSymbol*)func);
+        const char *prefix    = CFCParcel_get_prefix(parcel);
+        const char *short_sym = CFCFunction_short_func_sym(func);
+
+        char *func_html = S_html_create_func(klass, func, prefix, short_sym);
+        result = CFCUtil_cat(result, func_html, NULL);
+        FREEMEM(func_html);
+    }
+
+    if (result[0] != '\0') {
+        result = CFCUtil_cat(result, "</dl>\n", NULL);
+    }
+
+    return result;
+}
+
+static char*
+S_html_create_methods(CFCClass *klass) {
+    char *methods_html  = CFCUtil_strdup("");
+    char *result;
+
+    for (CFCClass *ancestor = klass;
+         ancestor;
+         ancestor = CFCClass_get_parent(ancestor)
+    ) {
+        const char *class_name = CFCClass_get_class_name(ancestor);
+        // Exclude methods inherited from Clownfish::Obj
+        if (ancestor != klass && strcmp(class_name, "Clownfish::Obj") == 0) {
+            break;
+        }
+
+        char *fresh_html = S_html_create_fresh_methods(klass, ancestor);
+        if (fresh_html[0] != '\0') {
+            if (ancestor == klass) {
+                methods_html = CFCUtil_cat(methods_html, fresh_html, NULL);
+            }
+            else {
+                methods_html
+                    = CFCUtil_cat(methods_html, "<h3>Methods inherited from ",
+                                  class_name, "</h3>\n", fresh_html, NULL);
+            }
+        }
+        FREEMEM(fresh_html);
+    }
+
+    if (methods_html[0] == '\0') {
+        result = CFCUtil_strdup("");
+    }
+    else {
+        result = CFCUtil_sprintf("<h2>Methods</h2>\n%s", methods_html);
+    }
+
+    FREEMEM(methods_html);
+    return result;
+}
+
+/** Return HTML for the fresh methods of `ancestor`.
+ */
+static char*
+S_html_create_fresh_methods(CFCClass *klass, CFCClass *ancestor) {
+    CFCMethod  **fresh_methods = CFCClass_fresh_methods(klass);
+    const char  *ancestor_name = CFCClass_get_class_name(ancestor);
+    char        *result        = CFCUtil_strdup("");
+
+    for (int meth_num = 0; fresh_methods[meth_num] != NULL; meth_num++) {
+        CFCMethod *method = fresh_methods[meth_num];
+        if (!CFCMethod_public(method)) {
+            continue;
+        }
+
+        const char *class_name = CFCMethod_get_class_name(method);
+        if (strcmp(class_name, ancestor_name) != 0) {
+            // The method is implementated in a subclass and already
+            // documented.
+            continue;
+        }
+
+        if (result[0] == '\0') {
+            result = CFCUtil_cat(result, "<dl>\n", NULL);
+        }
+
+        const char *macro_sym = CFCMethod_get_macro_sym(method);
+        result = CFCUtil_cat(result, "<dt id=\"func_", macro_sym, "\">",
+                             macro_sym, NULL);
+        if (CFCMethod_abstract(method)) {
+            result = CFCUtil_cat(result,
+                    " <span class=\"comment\">(abstract)</span>", NULL);
+        }
+        result = CFCUtil_cat(result, "</dt>\n", NULL);
+
+        CFCParcel  *parcel    = CFCSymbol_get_parcel((CFCSymbol*)method);
+        const char *prefix    = CFCParcel_get_PREFIX(parcel);
+        char       *short_sym = CFCMethod_short_method_sym(method, klass);
+        char *method_html = S_html_create_func(klass, (CFCFunction*)method,
+                                               prefix, short_sym);
+        result = CFCUtil_cat(result, method_html, NULL);
+        FREEMEM(method_html);
+        FREEMEM(short_sym);
+    }
+
+    if (result[0] != '\0') {
+        result = CFCUtil_cat(result, "</dl>\n", NULL);
+    }
+
+    return result;
+}
+
+static char*
+S_html_create_func(CFCClass *klass, CFCFunction *func, const char *prefix,
+                   const char *short_sym) {
+    CFCType    *return_type      = CFCFunction_get_return_type(func);
+    char       *return_type_html = S_type_to_html(klass, return_type);
+    const char *incremented      = "";
+
+    if (CFCType_incremented(return_type)) {
+        incremented = " <span class=\"comment\">// incremented</span>";
+    }
+
+    char *param_list = S_html_create_param_list(klass, func);
+
+    const char *pattern =
+        "<dd>\n"
+        "<pre><code>%s%s\n"
+        "<span class=\"prefix\">%s</span><strong>%s</strong>%s</code></pre>\n";
+    char *result = CFCUtil_sprintf(pattern, return_type_html, incremented,
+                                   prefix, short_sym, param_list);
+
+    FREEMEM(param_list);
+
+    // Get documentation, which may be inherited.
+    CFCDocuComment *docucomment = CFCFunction_get_docucomment(func);
+    if (!docucomment) {
+        const char *micro_sym = CFCFunction_micro_sym(func);
+        CFCClass *parent = klass;
+        while (NULL != (parent = CFCClass_get_parent(parent))) {
+            CFCFunction *parent_func
+                = (CFCFunction*)CFCClass_method(parent, micro_sym);
+            if (!parent_func) { break; }
+            docucomment = CFCFunction_get_docucomment(parent_func);
+            if (docucomment) { break; }
+        }
+    }
+
+    if (docucomment) {
+        // Description
+        const char *raw_desc = CFCDocuComment_get_description(docucomment);
+        char *desc = S_md_to_html(klass, raw_desc);
+        result = CFCUtil_cat(result, desc, NULL);
+        FREEMEM(desc);
+
+        // Params
+        const char **param_names
+            = CFCDocuComment_get_param_names(docucomment);
+        const char **param_docs
+            = CFCDocuComment_get_param_docs(docucomment);
+        if (param_names[0]) {
+            result = CFCUtil_cat(result, "<dl>\n", NULL);
+            for (size_t i = 0; param_names[i] != NULL; i++) {
+                char *doc = S_md_to_html(klass, param_docs[i]);
+                result = CFCUtil_cat(result, "<dt>", param_names[i],
+                                     "</dt>\n<dd>", doc, "</dd>\n",
+                                     NULL);
+                FREEMEM(doc);
+            }
+            result = CFCUtil_cat(result, "</dl>\n", NULL);
+        }
+
+        // Return value
+        const char *retval_doc = CFCDocuComment_get_retval(docucomment);
+        if (retval_doc && strlen(retval_doc)) {
+            char *md = CFCUtil_sprintf("**Returns:** %s", retval_doc);
+            char *html = S_md_to_html(klass, md);
+            result = CFCUtil_cat(result, html, NULL);
+            FREEMEM(html);
+            FREEMEM(md);
+        }
+    }
+
+    result = CFCUtil_cat(result, "</dd>\n", NULL);
+
+    FREEMEM(return_type_html);
+    return result;
+}
+
+static char*
+S_html_create_param_list(CFCClass *klass, CFCFunction *func) {
+    CFCParamList  *param_list = CFCFunction_get_param_list(func);
+    CFCVariable  **variables  = CFCParamList_get_variables(param_list);
+
+    const char *cfc_class = CFCBase_get_cfc_class((CFCBase*)func);
+    int is_method = strcmp(cfc_class, "Clownfish::CFC::Model::Method") == 0;
+
+    if (!variables[0]) {
+        return CFCUtil_strdup("(void);\n");
+    }
+
+    char *result = CFCUtil_strdup("(");
+
+    for (int i = 0; variables[i]; ++i) {
+        CFCVariable *variable = variables[i];
+        CFCType     *type     = CFCVariable_get_type(variable);
+        const char  *name     = CFCVariable_micro_sym(variable);
+
+        char *type_html;
+        if (is_method && i == 0) {
+            const char *prefix     = CFCClass_get_prefix(klass);
+            const char *struct_sym = CFCClass_get_struct_sym(klass);
+            const char *pattern    = "<span class=\"prefix\">%s</span>%s*";
+            type_html = CFCUtil_sprintf(pattern, prefix, struct_sym);
+        }
+        else {
+            type_html = S_type_to_html(klass, type);
+        }
+
+        result = CFCUtil_cat(result, "\n    ", type_html, " <strong>", name,
+                             "</strong>", NULL);
+
+        if (variables[i+1]) {
+            result = CFCUtil_cat(result, ",", NULL);
+        }
+        if (CFCType_decremented(type)) {
+            result = CFCUtil_cat(result,
+                    " <span class=\"comment\">// decremented</span>", NULL);
+        }
+
+        FREEMEM(type_html);
+    }
+
+    result = CFCUtil_cat(result, "\n);\n", NULL);
+
+    return result;
+}
+
+static char*
+S_html_create_inheritance(CFCClass *klass) {
+    CFCClass *ancestor = CFCClass_get_parent(klass);
+    char     *result   = CFCUtil_strdup("");
+
+    if (!ancestor) { return result; }
+
+    const char *class_name = CFCClass_get_class_name(klass);
+    result = CFCUtil_cat(result, "<h2>Inheritance</h2>\n<p>", class_name,
+                         NULL);
+    while (ancestor) {
+        const char *ancestor_name = CFCClass_get_class_name(ancestor);
+        const char *ancestor_sym  = CFCClass_full_struct_sym(ancestor);
+        result = CFCUtil_cat(result, " is a <a href=\"", ancestor_sym,
+                             ".html\">", ancestor_name, "</a>", NULL);
+        ancestor = CFCClass_get_parent(ancestor);
+    }
+    result = CFCUtil_cat(result, ".</p>\n", NULL);
+
+    return result;
+}
+
+static char*
+S_md_to_html(CFCClass *klass, const char *md) {
+    cmark_node *doc = cmark_parse_document(md, strlen(md));
+    S_convert_uris(klass, doc);
+    char *html = cmark_render_html(doc);
+    cmark_node_free(doc);
+
+    return html;
+}
+
+static void
+S_convert_uris(CFCClass *klass, cmark_node *node) {
+    cmark_node *cur = node;
+
+    while (cur) {
+        cmark_node_type type = cmark_node_get_type(cur);
+
+        // Find the next node in the tree before possibly deleting cur.
+        cmark_node *tree_next = NULL;
+
+        cmark_node *child = cmark_node_first_child(cur);
+        // Don't descend into links.
+        if (type != CMARK_NODE_LINK && child) {
+            tree_next = child;
+        }
+        else {
+            cmark_node *ancestor = cur;
+            while (ancestor != node) {
+                cmark_node *next = cmark_node_next(ancestor);
+                if (next) {
+                    tree_next = next;
+                    break;
+                }
+                ancestor = cmark_node_parent(ancestor);
+            }
+        }
+
+        if (type == NODE_LINK) {
+            S_convert_uri(klass, cur);
+        }
+
+        cur = tree_next;
+    }
+}
+
+static void
+S_convert_uri(CFCClass *klass, cmark_node *link) {
+    const char *uri = cmark_node_get_url(link);
+    if (!uri || !CFCUri_is_clownfish_uri(uri)) {
+        return;
+    }
+
+    char   *new_uri = NULL;
+    CFCUri *uri_obj = CFCUri_new(uri, klass);
+    int     type    = CFCUri_get_type(uri_obj);
+
+    switch (type) {
+        case CFC_URI_CLASS: {
+            const char *struct_sym = CFCUri_full_struct_sym(uri_obj);
+            new_uri = CFCUtil_sprintf("%s.html", struct_sym);
+            break;
+        }
+
+        case CFC_URI_FUNCTION:
+        case CFC_URI_METHOD: {
+            const char *struct_sym = CFCUri_full_struct_sym(uri_obj);
+            const char *func_sym   = CFCUri_get_func_sym(uri_obj);
+            new_uri = CFCUtil_sprintf("%s.html#func_%s", struct_sym, func_sym);
+            break;
+        }
+    }
+
+    if (new_uri) {
+        cmark_node_set_url(link, new_uri);
+
+        if (!cmark_node_first_child(link)) {
+            // Empty link text.
+            char *link_text = CFCC_link_text(uri_obj, klass);
+
+            if (link_text) {
+                cmark_node *text_node = cmark_node_new(CMARK_NODE_TEXT);
+                cmark_node_set_string_content(text_node, link_text);
+                cmark_node_append_child(link, text_node);
+                FREEMEM(link_text);
+            }
+        }
+    }
+    else {
+        // Remove link.
+        cmark_node *child = cmark_node_first_child(link);
+        while (child) {
+            cmark_node *next = cmark_node_next(child);
+            cmark_node_insert_before(link, child);
+            child = next;
+        }
+        cmark_node_free(link);
+    }
+
+    CFCBase_decref((CFCBase*)uri_obj);
+    FREEMEM(new_uri);
+}
+
+static char*
+S_type_to_html(CFCClass *klass, CFCType *type) {
+    const char *type_c = CFCType_to_c(type);
+
+    if (CFCType_is_object(type)) {
+        const char *struct_sym = CFCClass_full_struct_sym(klass);
+        const char *specifier  = CFCType_get_specifier(type);
+        const char *underscore = strchr(type_c, '_');
+
+        if (underscore) {
+            size_t  offset = underscore + 1 - type_c;
+            char   *prefix = CFCUtil_strndup(specifier, offset);
+            char   *retval;
+
+            if (strcmp(specifier, struct_sym) == 0) {
+                // Don't link types of the same class.
+                retval = CFCUtil_sprintf("<span class=\"prefix\">%s</span>%s",
+                                         prefix, type_c + offset);
+            }
+            else {
+                const char *pattern =
+                    "<span class=\"prefix\">%s</span>"
+                    "<a href=\"%s.html\">"
+                    "%s"
+                    "</a>";
+                retval = CFCUtil_sprintf(pattern, prefix, specifier,
+                                         type_c + offset);
+            }
+
+            FREEMEM(prefix);
+            return retval;
+        }
+    }
+
+    return CFCUtil_strdup(type_c);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCCHtml.h
----------------------------------------------------------------------
diff --git a/compiler/src/CFCCHtml.h b/compiler/src/CFCCHtml.h
new file mode 100644
index 0000000..dc5a5f7
--- /dev/null
+++ b/compiler/src/CFCCHtml.h
@@ -0,0 +1,71 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_CFCCHTML
+#define H_CFCCHTML
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct CFCCHtml CFCCHtml;
+struct CFCClass;
+struct CFCHierarchy;
+struct CFCParcel;
+
+/** Clownfish::CFC::Binding::C::Html - Generate C API documentation in HTML
+ * format.
+ */
+
+/** Constructor.
+ *
+ * @param header HTML header.
+ * @param footer HTML footer.
+ */
+CFCCHtml*
+CFCCHtml_new(struct CFCHierarchy *hierarchy, const char *header,
+             const char *footer);
+
+CFCCHtml*
+CFCCHtml_init(CFCCHtml *self, struct CFCHierarchy *hierarchy,
+              const char *header, const char *footer);
+
+void
+CFCCHtml_destroy(CFCCHtml *self);
+
+/** Write the HTML documentation.
+ */
+void
+CFCCHtml_write_html_docs(CFCCHtml *self);
+
+/** Return the index document of the HTML documentation for `parcel`
+ * or NULL if there aren't any public classes in the parcel.
+ */
+char*
+CFCCHtml_create_index_doc(CFCCHtml *self, struct CFCParcel *parcel,
+                          struct CFCClass **classes);
+
+/** Return the HTML documentation for the class.
+ */
+char*
+CFCCHtml_create_html_doc(CFCCHtml *self, struct CFCClass *klass);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_CFCCHTML */
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCUtil.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCUtil.c b/compiler/src/CFCUtil.c
index 6e99858..89c5721 100644
--- a/compiler/src/CFCUtil.c
+++ b/compiler/src/CFCUtil.c
@@ -237,6 +237,12 @@ CFCUtil_make_c_comment(const char *text) {
 }
 
 char*
+CFCUtil_make_html_comment(const char *text) {
+    if (text && text[0] == '\0') { return CFCUtil_strdup(text); }
+    return CFCUtil_enclose_lines(text, "", "", "<!--\n", "-->\n");
+}
+
+char*
 CFCUtil_make_perl_comment(const char *text) {
     return CFCUtil_enclose_lines(text, "# ", "", "", "");
 }

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/faa11a1e/compiler/src/CFCUtil.h
----------------------------------------------------------------------
diff --git a/compiler/src/CFCUtil.h b/compiler/src/CFCUtil.h
index 5c528d9..0b1a751 100644
--- a/compiler/src/CFCUtil.h
+++ b/compiler/src/CFCUtil.h
@@ -88,6 +88,11 @@ CFCUtil_enclose_lines(const char *text, const char 
*line_prefix,
 char*
 CFCUtil_make_c_comment(const char *text);
 
+/** Create a HTML comment.
+ */
+char*
+CFCUtil_make_html_comment(const char *text);
+
 /** Create a Perl comment.
  */
 char*

Reply via email to