| /* 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 <stdlib.h> |
| #include <stddef.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 "CFCDocument.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" |
| #include "CFCCallable.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; |
| char *index_filename; |
| }; |
| |
| 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}"; |
| |
| char* |
| S_create_index_doc(CFCCHtml *self, CFCClass **classes, CFCDocument **docs); |
| |
| char* |
| S_create_standalone_doc(CFCCHtml *self, CFCDocument *doc); |
| |
| static int |
| S_compare_class_name(const void *va, const void *vb); |
| |
| static int |
| S_compare_doc_path(const void *va, const void *vb); |
| |
| 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, CFCCallable *func, const char *prefix, |
| const char *short_sym); |
| |
| static char* |
| S_html_create_param_list(CFCClass *klass, CFCCallable *func); |
| |
| static char* |
| S_html_create_inheritance(CFCClass *klass); |
| |
| static char* |
| S_md_to_html(const char *md, CFCClass *klass, int dir_level); |
| |
| static void |
| S_transform_doc(cmark_node *node, CFCClass *klass, int dir_level); |
| |
| static int |
| S_transform_code_block(cmark_node *node, int found_matching_code_block); |
| |
| static void |
| S_transform_link(cmark_node *link, CFCClass *klass, int dir_level); |
| |
| static char* |
| S_type_to_html(CFCType *type, const char *sep, CFCClass *doc_class); |
| |
| static char* |
| S_cfc_uri_to_url(CFCUri *uri_obj, CFCClass *base, int dir_level); |
| |
| static char* |
| S_class_to_url(CFCClass *klass, CFCClass *base, int dir_level); |
| |
| static char* |
| S_document_to_url(CFCDocument *doc, CFCClass *base, int dir_level); |
| |
| static char* |
| S_relative_url(const char *url, CFCClass *base, int dir_level); |
| |
| 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); |
| FREEMEM(self->index_filename); |
| CFCBase_destroy((CFCBase*)self); |
| } |
| |
| void |
| CFCCHtml_write_html_docs(CFCCHtml *self) { |
| CFCHierarchy *hierarchy = self->hierarchy; |
| CFCClass **ordered = CFCHierarchy_ordered_classes(hierarchy); |
| CFCDocument **doc_registry = CFCDocument_get_registry(); |
| const char *doc_path = self->doc_path; |
| |
| size_t num_classes = 0; |
| for (size_t i = 0; ordered[i] != NULL; i++) { |
| ++num_classes; |
| } |
| |
| size_t num_md_docs = 0; |
| for (size_t i = 0; doc_registry[i] != NULL; i++) { |
| ++num_md_docs; |
| } |
| |
| // Clone doc registry. |
| size_t bytes = (num_md_docs + 1) * sizeof(CFCDocument*); |
| CFCDocument **md_docs = (CFCDocument**)MALLOCATE(bytes); |
| memcpy(md_docs, doc_registry, bytes); |
| |
| qsort(ordered, num_classes, sizeof(*ordered), S_compare_class_name); |
| qsort(md_docs, num_md_docs, sizeof(*md_docs), S_compare_doc_path); |
| |
| size_t max_docs = 1 + num_classes + num_md_docs; |
| 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. |
| |
| char *index_doc = S_create_index_doc(self, ordered, md_docs); |
| if (index_doc != NULL) { |
| filenames[num_docs] = CFCUtil_strdup(self->index_filename); |
| html_docs[num_docs] = index_doc; |
| num_docs++; |
| } |
| |
| for (size_t i = 0; ordered[i] != NULL; i++) { |
| CFCClass *klass = ordered[i]; |
| if (CFCClass_included(klass) || !CFCClass_public(klass)) { |
| continue; |
| } |
| |
| const char *class_name = CFCClass_get_name(klass); |
| char *path = CFCUtil_global_replace(class_name, "::", CHY_DIR_SEP); |
| filenames[num_docs] = CFCUtil_sprintf("%s.html", path); |
| html_docs[num_docs] = CFCCHtml_create_html_doc(self, klass); |
| ++num_docs; |
| |
| FREEMEM(path); |
| } |
| |
| for (size_t i = 0; md_docs[i] != NULL; i++) { |
| CFCDocument *md_doc = md_docs[i]; |
| const char *path = CFCDocument_get_path_part(md_doc); |
| filenames[num_docs] = CFCUtil_sprintf("%s.html", path); |
| html_docs[num_docs] = S_create_standalone_doc(self, md_doc); |
| ++num_docs; |
| } |
| |
| // Write out docs. |
| |
| 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(md_docs); |
| FREEMEM(ordered); |
| } |
| |
| char* |
| S_create_index_doc(CFCCHtml *self, CFCClass **classes, CFCDocument **docs) { |
| CFCParcel **parcels = CFCParcel_all_parcels(); |
| |
| // Compile standalone document list. |
| |
| char *doc_list = CFCUtil_strdup(""); |
| |
| for (size_t i = 0; docs[i] != NULL; i++) { |
| CFCDocument *doc = docs[i]; |
| |
| const char *path_part = CFCDocument_get_path_part(doc); |
| char *url = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "/"); |
| char *name = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "::"); |
| doc_list |
| = CFCUtil_cat(doc_list, "<li><a href=\"", url, ".html\">", |
| name, "</a></li>\n", NULL); |
| FREEMEM(name); |
| FREEMEM(url); |
| } |
| |
| if (doc_list[0] != '\0') { |
| const char *pattern = |
| "<h2>Documentation</h2>\n" |
| "<ul>\n" |
| "%s" |
| "</ul>\n"; |
| char *contents = doc_list; |
| doc_list = CFCUtil_sprintf(pattern, contents); |
| FREEMEM(contents); |
| } |
| |
| // Compile class lists per parcel. |
| |
| char *class_lists = CFCUtil_strdup(""); |
| char *parcel_names = CFCUtil_strdup(""); |
| char *filename = CFCUtil_strdup(""); |
| |
| for (size_t i = 0; parcels[i]; i++) { |
| CFCParcel *parcel = parcels[i]; |
| if (CFCParcel_included(parcel)) { continue; } |
| |
| 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 |
| || !CFCClass_public(klass) |
| ) { |
| continue; |
| } |
| |
| const char *class_name = CFCClass_get_name(klass); |
| char *url = S_class_to_url(klass, NULL, 0); |
| class_list |
| = CFCUtil_cat(class_list, "<li><a href=\"", url, "\">", |
| class_name, "</a></li>\n", NULL); |
| FREEMEM(url); |
| } |
| |
| if (class_list[0] != '\0') { |
| const char *pattern = |
| "<h2>Classes in parcel %s</h2>\n" |
| "<ul>\n" |
| "%s" |
| "</ul>\n"; |
| char *html = CFCUtil_sprintf(pattern, parcel_name, class_list); |
| class_lists = CFCUtil_cat(class_lists, html, NULL); |
| FREEMEM(html); |
| |
| const char *parcel_name = CFCParcel_get_name(parcel); |
| const char *sep = parcel_names[0] == '\0' ? "" : ", "; |
| parcel_names = CFCUtil_cat(parcel_names, sep, parcel_name, NULL); |
| |
| const char *parcel_prefix = CFCParcel_get_prefix(parcel); |
| filename = CFCUtil_cat(filename, parcel_prefix, NULL); |
| } |
| |
| FREEMEM(class_list); |
| } |
| |
| // Create doc. |
| |
| char *title = CFCUtil_sprintf("%s " UTF8_NDASH " C API Index", |
| parcel_names); |
| char *header = CFCUtil_global_replace(self->header, "{title}", title); |
| |
| const char pattern[] = |
| "%s" |
| "<h1>%s</h1>\n" |
| "%s" |
| "%s" |
| "%s"; |
| char *doc |
| = CFCUtil_sprintf(pattern, header, title, doc_list, class_lists, |
| self->footer); |
| |
| // Create filename |
| |
| if (filename[0] == '\0') { |
| for (size_t i = 0; parcels[i]; i++) { |
| CFCParcel *parcel = parcels[i]; |
| if (CFCParcel_included(parcel)) { continue; } |
| const char *prefix = CFCParcel_get_prefix(parcel); |
| filename = CFCUtil_cat(filename, prefix, NULL); |
| } |
| } |
| |
| char *retval = NULL; |
| |
| if (filename[0] != '\0') { |
| // Removing trailing underscore. |
| size_t filename_len = strlen(filename); |
| filename[filename_len-1] = '\0'; |
| |
| // Add .html extension. |
| char *base = filename; |
| filename = CFCUtil_sprintf("%s.html", base); |
| FREEMEM(base); |
| |
| retval = doc; |
| doc = NULL; |
| |
| FREEMEM(self->index_filename); |
| self->index_filename = filename; |
| filename = NULL; |
| } |
| |
| FREEMEM(doc); |
| FREEMEM(header); |
| FREEMEM(title); |
| FREEMEM(filename); |
| FREEMEM(parcel_names); |
| FREEMEM(class_lists); |
| FREEMEM(doc_list); |
| |
| return retval; |
| } |
| |
| char* |
| S_create_standalone_doc(CFCCHtml *self, CFCDocument *doc) { |
| const char *path = CFCDocument_get_path_part(doc); |
| char *title = CFCUtil_global_replace(path, CHY_DIR_SEP, "::"); |
| char *header = CFCUtil_global_replace(self->header, "{title}", title); |
| |
| char *md = CFCDocument_get_contents(doc); |
| int dir_level = 0; |
| for (size_t i = 0; path[i]; i++) { |
| if (path[i] == CHY_DIR_SEP_CHAR) { ++dir_level; } |
| } |
| char *body = S_md_to_html(md, NULL, dir_level); |
| |
| char *html_doc = CFCUtil_sprintf("%s%s%s", header, body, self->footer); |
| |
| FREEMEM(body); |
| FREEMEM(md); |
| FREEMEM(header); |
| FREEMEM(title); |
| return html_doc; |
| } |
| |
| char* |
| CFCCHtml_create_html_doc(CFCCHtml *self, CFCClass *klass) { |
| const char *class_name = CFCClass_get_name(klass); |
| char *title |
| = CFCUtil_sprintf("%s " UTF8_NDASH " C API Documentation", class_name); |
| char *header = CFCUtil_global_replace(self->header, "{title}", title); |
| char *body = CFCCHtml_create_html_body(self, klass); |
| |
| char *html_doc = CFCUtil_sprintf("%s%s%s", header, body, self->footer); |
| |
| FREEMEM(body); |
| FREEMEM(header); |
| FREEMEM(title); |
| return html_doc; |
| } |
| |
| char* |
| CFCCHtml_create_html_body(CFCCHtml *self, CFCClass *klass) { |
| if (self->index_filename == NULL) { |
| // Create index filename by creating index doc. |
| CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy); |
| CFCDocument **docs = CFCDocument_get_registry(); |
| char *index_doc = S_create_index_doc(self, ordered, docs); |
| FREEMEM(index_doc); |
| FREEMEM(ordered); |
| |
| if (self->index_filename == NULL) { |
| CFCUtil_die("Empty hierarchy"); |
| } |
| } |
| |
| CFCParcel *parcel = CFCClass_get_parcel(klass); |
| const char *parcel_name = CFCParcel_get_name(parcel); |
| const char *prefix = CFCClass_get_prefix(klass); |
| const char *PREFIX = CFCClass_get_PREFIX(klass); |
| const char *class_name = CFCClass_get_name(klass); |
| const char *class_nickname = CFCClass_get_nickname(klass); |
| const char *class_var = CFCClass_short_class_var(klass); |
| const char *struct_sym = CFCClass_get_struct_sym(klass); |
| const char *include_h = CFCClass_include_h(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 *index_url = S_relative_url(self->index_filename, klass, 0); |
| |
| // Put it all together. |
| const char pattern[] = |
| "<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 variable</td>\n" |
| "<td><code><span class=\"prefix\">%s</span>%s</code></td>\n" |
| "</tr>\n" |
| "<tr>\n" |
| "<td class=\"label\">struct symbol</td>\n" |
| "<td><code><span class=\"prefix\">%s</span>%s</code></td>\n" |
| "</tr>\n" |
| "<tr>\n" |
| "<td class=\"label\">class nickname</td>\n" |
| "<td><code><span class=\"prefix\">%s</span>%s</code></td>\n" |
| "</tr>\n" |
| "<tr>\n" |
| "<td class=\"label\">header file</td>\n" |
| "<td><code>%s</code></td>\n" |
| "</tr>\n" |
| "</table>\n" |
| "%s" |
| "%s" |
| "%s" |
| "%s" |
| "%s" |
| "%s"; |
| char *html_body |
| = CFCUtil_sprintf(pattern, class_name, index_url, |
| parcel_name, PREFIX, class_var, prefix, struct_sym, |
| prefix, class_nickname, include_h, name, synopsis, |
| description, functions_html, methods_html, |
| inheritance); |
| |
| FREEMEM(index_url); |
| FREEMEM(name); |
| FREEMEM(synopsis); |
| FREEMEM(description); |
| FREEMEM(functions_html); |
| FREEMEM(methods_html); |
| FREEMEM(inheritance); |
| |
| return html_body; |
| } |
| |
| static int |
| S_compare_class_name(const void *va, const void *vb) { |
| const char *a = CFCClass_get_name(*(CFCClass**)va); |
| const char *b = CFCClass_get_name(*(CFCClass**)vb); |
| |
| return strcmp(a, b); |
| } |
| |
| static int |
| S_compare_doc_path(const void *va, const void *vb) { |
| const char *a = CFCDocument_get_path_part(*(CFCDocument**)va); |
| const char *b = CFCDocument_get_path_part(*(CFCDocument**)vb); |
| |
| return strcmp(a, b); |
| } |
| |
| static char* |
| S_html_create_name(CFCClass *klass) { |
| const char *class_name = CFCClass_get_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(md, klass, 0); |
| |
| 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(raw_desc, klass, 0); |
| } |
| } |
| |
| 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); |
| const char *prefix = CFCClass_get_prefix(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 *name = CFCFunction_get_name(func); |
| result = CFCUtil_cat(result, "<dt id=\"func_", name, "\">", |
| name, "</dt>\n", NULL); |
| |
| char *short_sym = CFCFunction_short_func_sym(func, klass); |
| char *func_html = S_html_create_func(klass, (CFCCallable*)func, prefix, |
| short_sym); |
| result = CFCUtil_cat(result, func_html, NULL); |
| FREEMEM(func_html); |
| FREEMEM(short_sym); |
| } |
| |
| 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_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(ancestor); |
| const char *prefix = CFCClass_get_prefix(klass); |
| 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 *name = CFCMethod_get_name(method); |
| if (strcmp(name, "Destroy") == 0) { |
| // Destroy must not be called directly. |
| continue; |
| } |
| |
| CFCMethod *other = CFCClass_method(klass, name); |
| if (!CFCMethod_is_fresh(other, ancestor)) { |
| // The method is implementated in a subclass and already |
| // documented. |
| continue; |
| } |
| |
| if (result[0] == '\0') { |
| result = CFCUtil_cat(result, "<dl>\n", NULL); |
| } |
| |
| result = CFCUtil_cat(result, "<dt id=\"func_", name, "\">", |
| name, NULL); |
| if (CFCMethod_abstract(method)) { |
| result = CFCUtil_cat(result, |
| " <span class=\"comment\">(abstract)</span>", NULL); |
| } |
| result = CFCUtil_cat(result, "</dt>\n", NULL); |
| |
| char *short_sym = CFCMethod_short_method_sym(method, klass); |
| char *method_html = S_html_create_func(klass, (CFCCallable*)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, CFCCallable *func, const char *prefix, |
| const char *short_sym) { |
| CFCType *ret_type = CFCCallable_get_return_type(func); |
| char *ret_html = S_type_to_html(ret_type, "", klass); |
| const char *ret_array = CFCType_get_array(ret_type); |
| const char *ret_array_str = ret_array ? ret_array : ""; |
| const char *incremented = ""; |
| |
| if (CFCType_incremented(ret_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%s\n" |
| "<span class=\"prefix\">%s</span><strong>%s</strong>%s</code></pre>\n"; |
| char *result = CFCUtil_sprintf(pattern, ret_html, ret_array_str, |
| incremented, prefix, short_sym, param_list); |
| |
| FREEMEM(param_list); |
| |
| // Get documentation, which may be inherited. |
| CFCDocuComment *docucomment = CFCCallable_get_docucomment(func); |
| if (!docucomment) { |
| const char *name = CFCCallable_get_name(func); |
| CFCClass *parent = klass; |
| while (NULL != (parent = CFCClass_get_parent(parent))) { |
| CFCCallable *parent_func |
| = (CFCCallable*)CFCClass_method(parent, name); |
| if (!parent_func) { break; } |
| docucomment = CFCCallable_get_docucomment(parent_func); |
| if (docucomment) { break; } |
| } |
| } |
| |
| if (docucomment) { |
| // Description |
| const char *raw_desc = CFCDocuComment_get_description(docucomment); |
| char *desc = S_md_to_html(raw_desc, klass, 0); |
| 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(param_docs[i], klass, 0); |
| 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(md, klass, 0); |
| result = CFCUtil_cat(result, html, NULL); |
| FREEMEM(html); |
| FREEMEM(md); |
| } |
| } |
| |
| result = CFCUtil_cat(result, "</dd>\n", NULL); |
| |
| FREEMEM(ret_html); |
| return result; |
| } |
| |
| static char* |
| S_html_create_param_list(CFCClass *klass, CFCCallable *func) { |
| CFCParamList *param_list = CFCCallable_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("(\n"); |
| |
| for (int i = 0; variables[i]; ++i) { |
| CFCVariable *variable = variables[i]; |
| CFCType *type = CFCVariable_get_type(variable); |
| const char *name = CFCVariable_get_name(variable); |
| const char *array = CFCType_get_array(type); |
| const char *array_str = array ? array : ""; |
| |
| 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(type, " ", klass); |
| } |
| |
| const char *sep = variables[i+1] ? "," : ""; |
| const char *decremented = ""; |
| |
| if (CFCType_decremented(type)) { |
| decremented = " <span class=\"comment\">// decremented</span>"; |
| } |
| |
| const char *pattern = " %s<strong>%s</strong>%s%s%s\n"; |
| char *param_html = CFCUtil_sprintf(pattern, type_html, name, array_str, |
| sep, decremented); |
| result = CFCUtil_cat(result, param_html, NULL); |
| |
| FREEMEM(param_html); |
| FREEMEM(type_html); |
| } |
| |
| result = CFCUtil_cat(result, ");\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_name(klass); |
| result = CFCUtil_cat(result, "<h2>Inheritance</h2>\n<p>", class_name, |
| NULL); |
| while (ancestor) { |
| const char *ancestor_name = CFCClass_get_name(ancestor); |
| char *ancestor_url = S_class_to_url(ancestor, klass, 0); |
| result = CFCUtil_cat(result, " is a <a href=\"", ancestor_url, "\">", |
| ancestor_name, "</a>", NULL); |
| FREEMEM(ancestor_url); |
| ancestor = CFCClass_get_parent(ancestor); |
| } |
| result = CFCUtil_cat(result, ".</p>\n", NULL); |
| |
| return result; |
| } |
| |
| static char* |
| S_md_to_html(const char *md, CFCClass *klass, int dir_level) { |
| int options = CMARK_OPT_SMART |
| | CMARK_OPT_VALIDATE_UTF8; |
| cmark_node *doc = cmark_parse_document(md, strlen(md), options); |
| S_transform_doc(doc, klass, dir_level); |
| char *html = cmark_render_html(doc, CMARK_OPT_SAFE); |
| cmark_node_free(doc); |
| |
| return html; |
| } |
| |
| static void |
| S_transform_doc(cmark_node *node, CFCClass *klass, int dir_level) { |
| int found_matching_code_block = false; |
| cmark_iter *iter = cmark_iter_new(node); |
| cmark_event_type ev_type; |
| |
| while (CMARK_EVENT_DONE != (ev_type = cmark_iter_next(iter))) { |
| cmark_node *cur = cmark_iter_get_node(iter); |
| cmark_node_type type = cmark_node_get_type(cur); |
| |
| switch (type) { |
| case CMARK_NODE_CODE_BLOCK: |
| found_matching_code_block |
| = S_transform_code_block(cur, found_matching_code_block); |
| break; |
| |
| case CMARK_NODE_LINK: |
| if (ev_type == CMARK_EVENT_EXIT) { |
| S_transform_link(cur, klass, dir_level); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| cmark_iter_free(iter); |
| } |
| |
| static int |
| S_transform_code_block(cmark_node *code_block, int found_matching_code_block) { |
| int is_host = CFCMarkdown_code_block_is_host(code_block, "c"); |
| |
| if (is_host) { |
| found_matching_code_block = true; |
| } |
| |
| if (CFCMarkdown_code_block_is_last(code_block)) { |
| if (!found_matching_code_block) { |
| cmark_node *warning |
| = cmark_node_new(CMARK_NODE_CODE_BLOCK); |
| cmark_node_set_literal(warning, |
| "Code example for C is missing"); |
| cmark_node_insert_after(code_block, warning); |
| } |
| else { |
| // Reset. |
| found_matching_code_block = false; |
| } |
| } |
| |
| if (!is_host) { cmark_node_free(code_block); } |
| |
| return found_matching_code_block; |
| } |
| |
| static void |
| S_transform_link(cmark_node *link, CFCClass *doc_class, int dir_level) { |
| const char *uri_string = cmark_node_get_url(link); |
| if (!uri_string || !CFCUri_is_clownfish_uri(uri_string)) { |
| return; |
| } |
| |
| CFCUri *uri_obj = CFCUri_new(uri_string, doc_class); |
| CFCUriType uri_type = CFCUri_get_type(uri_obj); |
| char *url = S_cfc_uri_to_url(uri_obj, doc_class, dir_level); |
| |
| if (uri_type == CFC_URI_NULL || uri_type == CFC_URI_ERROR) { |
| // Replace link with text. |
| char *link_text = CFCC_link_text(uri_obj); |
| cmark_node *text_node = cmark_node_new(CMARK_NODE_TEXT); |
| cmark_node_set_literal(text_node, link_text); |
| cmark_node_insert_after(link, text_node); |
| cmark_node_free(link); |
| FREEMEM(link_text); |
| } |
| else if (url) { |
| cmark_node_set_url(link, url); |
| |
| if (!cmark_node_first_child(link)) { |
| // Empty link text. |
| char *link_text = CFCC_link_text(uri_obj); |
| |
| if (link_text) { |
| cmark_node *text_node = cmark_node_new(CMARK_NODE_TEXT); |
| cmark_node_set_literal(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(url); |
| } |
| |
| static char* |
| S_type_to_html(CFCType *type, const char *sep, CFCClass *doc_class) { |
| const char *specifier = CFCType_get_specifier(type); |
| char *specifier_html = NULL; |
| |
| if (CFCType_is_object(type)) { |
| CFCClass *klass = NULL; |
| |
| // Don't link to doc class. |
| if (strcmp(specifier, CFCClass_full_struct_sym(doc_class)) != 0) { |
| klass = CFCClass_fetch_by_struct_sym(specifier); |
| if (!klass) { |
| CFCUtil_warn("Class '%s' not found", specifier); |
| } |
| else if (!CFCClass_public(klass)) { |
| CFCUtil_warn("Non-public class '%s' used in public method", |
| specifier); |
| klass = NULL; |
| } |
| } |
| |
| const char *underscore = strchr(specifier, '_'); |
| if (!underscore) { |
| CFCUtil_die("Unprefixed object specifier '%s'", specifier); |
| } |
| |
| ptrdiff_t offset = underscore + 1 - specifier; |
| char *prefix = CFCUtil_strndup(specifier, (size_t)offset); |
| const char *struct_sym = specifier + offset; |
| |
| if (!klass) { |
| const char *pattern = "<span class=\"prefix\">%s</span>%s"; |
| specifier_html = CFCUtil_sprintf(pattern, prefix, struct_sym); |
| } |
| else { |
| char *url = S_class_to_url(klass, doc_class, 0); |
| const char *pattern = |
| "<span class=\"prefix\">%s</span>" |
| "<a href=\"%s\">%s</a>"; |
| specifier_html = CFCUtil_sprintf(pattern, prefix, url, struct_sym); |
| FREEMEM(url); |
| } |
| |
| FREEMEM(prefix); |
| } |
| else { |
| specifier_html = CFCUtil_strdup(specifier); |
| } |
| |
| const char *const_str = CFCType_const(type) ? "const " : ""; |
| |
| int indirection = CFCType_get_indirection(type); |
| ptrdiff_t asterisk_offset = indirection < 10 ? 10 - indirection : 0; |
| const char *asterisks = "**********"; |
| const char *ind_str = asterisks + asterisk_offset; |
| |
| char *html = CFCUtil_sprintf("%s%s%s%s", const_str, specifier_html, |
| sep, ind_str); |
| |
| FREEMEM(specifier_html); |
| return html; |
| } |
| |
| // Return a relative URL for a CFCUri object. |
| static char* |
| S_cfc_uri_to_url(CFCUri *uri_obj, CFCClass *doc_class, int dir_level) { |
| char *url = NULL; |
| CFCUriType type = CFCUri_get_type(uri_obj); |
| |
| switch (type) { |
| case CFC_URI_CLASS: { |
| CFCClass *klass = CFCUri_get_class(uri_obj); |
| url = S_class_to_url(klass, doc_class, dir_level); |
| break; |
| } |
| |
| case CFC_URI_FUNCTION: |
| case CFC_URI_METHOD: { |
| CFCClass *klass = CFCUri_get_class(uri_obj); |
| const char *name = CFCUri_get_callable_name(uri_obj); |
| char *class_url = S_class_to_url(klass, doc_class, dir_level); |
| url = CFCUtil_sprintf("%s#func_%s", class_url, name); |
| FREEMEM(class_url); |
| break; |
| } |
| |
| case CFC_URI_DOCUMENT: { |
| CFCDocument *doc = CFCUri_get_document(uri_obj); |
| url = S_document_to_url(doc, doc_class, dir_level); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| return url; |
| } |
| |
| // Return a relative URL to a class. |
| static char* |
| S_class_to_url(CFCClass *klass, CFCClass *base, int dir_level) { |
| const char *class_name = CFCClass_get_name(klass); |
| char *path = CFCUtil_global_replace(class_name, "::", CHY_DIR_SEP); |
| char *url = CFCUtil_sprintf("%s.html", path); |
| char *rel_url = S_relative_url(url, base, dir_level); |
| |
| FREEMEM(url); |
| FREEMEM(path); |
| return rel_url; |
| } |
| |
| // Return a relative URL to a document. |
| static char* |
| S_document_to_url(CFCDocument *doc, CFCClass *base, int dir_level) { |
| const char *path_part = CFCDocument_get_path_part(doc); |
| char *slashy = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "/"); |
| char *url = CFCUtil_sprintf("%s.html", slashy); |
| char *rel_url = S_relative_url(url, base, dir_level); |
| |
| FREEMEM(url); |
| FREEMEM(slashy); |
| return rel_url; |
| } |
| |
| static char* |
| S_relative_url(const char *url, CFCClass *base, int dir_level) { |
| if (base) { |
| const char *base_name = CFCClass_get_name(base); |
| for (size_t i = 0; base_name[i]; i++) { |
| if (base_name[i] == ':' && base_name[i+1] == ':') { |
| dir_level++; |
| i++; |
| } |
| } |
| } |
| |
| // Create path back to root |
| size_t bytes = (size_t)(dir_level * 3); |
| char *prefix = (char*)MALLOCATE(bytes + 1); |
| for (size_t i = 0; i < bytes; i += 3) { |
| memcpy(prefix + i, "../", 3); |
| } |
| prefix[bytes] = '\0'; |
| |
| char *rel_url = CFCUtil_sprintf("%s%s", prefix, url); |
| |
| FREEMEM(prefix); |
| return rel_url; |
| } |
| |