Convert Markdown to POD
Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/c2a78e23 Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/c2a78e23 Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/c2a78e23 Branch: refs/heads/master Commit: c2a78e237fd3ba30557be12c6a646e778c276944 Parents: 7d9a311 Author: Nick Wellnhofer <[email protected]> Authored: Mon Nov 10 17:35:22 2014 +0100 Committer: Nick Wellnhofer <[email protected]> Committed: Wed Dec 24 16:02:04 2014 +0100 ---------------------------------------------------------------------- compiler/perl/lib/Clownfish/CFC.xs | 9 +- compiler/src/CFCClass.c | 8 + compiler/src/CFCClass.h | 8 + compiler/src/CFCPerlClass.c | 34 ++- compiler/src/CFCPerlPod.c | 429 ++++++++++++++++++++++++++++---- compiler/src/CFCPerlPod.h | 3 +- 6 files changed, 421 insertions(+), 70 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/perl/lib/Clownfish/CFC.xs ---------------------------------------------------------------------- diff --git a/compiler/perl/lib/Clownfish/CFC.xs b/compiler/perl/lib/Clownfish/CFC.xs index 408c058..a27e193 100644 --- a/compiler/perl/lib/Clownfish/CFC.xs +++ b/compiler/perl/lib/Clownfish/CFC.xs @@ -2461,11 +2461,12 @@ PPCODE: SV* -_perlify_doc_text(self, source) - CFCPerlPod *self; - const char *source; +_md_to_pod(self, klass, source) + CFCPerlPod *self; + CFCClass *klass; + const char *source; CODE: - RETVAL = S_sv_eat_c_string(CFCPerlPod_perlify_doc_text(self, source)); + RETVAL = S_sv_eat_c_string(CFCPerlPod_md_to_pod(self, klass, source)); OUTPUT: RETVAL SV* http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/src/CFCClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCClass.c b/compiler/src/CFCClass.c index 37abb3c..2184f57 100644 --- a/compiler/src/CFCClass.c +++ b/compiler/src/CFCClass.c @@ -319,6 +319,14 @@ CFCClass_fetch_singleton(CFCParcel *parcel, const char *class_name) { } char key[MAX_SINGLETON_LEN + 1]; sprintf(key, "%s%s", prefix, struct_sym); + + return CFCClass_fetch_by_struct_sym(key); +} + +CFCClass* +CFCClass_fetch_by_struct_sym(const char *key) { + CFCUTIL_NULL_CHECK(key); + for (size_t i = 0; i < registry_size; i++) { if (strcmp(registry[i].key, key) == 0) { return registry[i].klass; http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/src/CFCClass.h ---------------------------------------------------------------------- diff --git a/compiler/src/CFCClass.h b/compiler/src/CFCClass.h index 1aa654e..b960134 100644 --- a/compiler/src/CFCClass.h +++ b/compiler/src/CFCClass.h @@ -82,6 +82,14 @@ CFCClass_destroy(CFCClass *self); CFCClass* CFCClass_fetch_singleton(struct CFCParcel *parcel, const char *class_name); +/** Retrieve a Class by its struct sym. + * + * @param A Clownfish::CFC::Model::Parcel. + * @param full_struct_sym The Class's full struct sym. + */ +CFCClass* +CFCClass_fetch_by_struct_sym(const char *full_struct_sym); + /** Empty out the registry, decrementing the refcount of all Class singleton * objects. */ http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/src/CFCPerlClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlClass.c b/compiler/src/CFCPerlClass.c index f9a3e20..782f719 100644 --- a/compiler/src/CFCPerlClass.c +++ b/compiler/src/CFCPerlClass.c @@ -359,14 +359,18 @@ CFCPerlClass_create_pod(CFCPerlClass *self) { // Get the class's brief description. const char *raw_brief = CFCDocuComment_get_brief(docucom); - char *brief = CFCPerlPod_perlify_doc_text(pod_spec, raw_brief); + char *brief = CFCPerlPod_md_to_pod(pod_spec, client, raw_brief); // Get the class's long description. - const char *raw_description = CFCPerlPod_get_description(pod_spec); - if (!raw_description || !strlen(raw_description)) { - raw_description = CFCDocuComment_get_long(docucom); + char *description; + const char *pod_description = CFCPerlPod_get_description(pod_spec); + if (pod_description && strlen(pod_description)) { + description = CFCUtil_sprintf("%s\n", pod_description); + } + else { + const char *raw_description = CFCDocuComment_get_long(docucom); + description = CFCPerlPod_md_to_pod(pod_spec, client, raw_description); } - char *description = CFCPerlPod_perlify_doc_text(pod_spec, raw_description); // Create SYNOPSIS. const char *raw_synopsis = CFCPerlPod_get_synopsis(pod_spec); @@ -399,27 +403,21 @@ CFCPerlClass_create_pod(CFCPerlClass *self) { ancestor_klass, NULL); } } - inheritance = CFCUtil_cat(inheritance, ".\n", NULL); + inheritance = CFCUtil_cat(inheritance, ".\n\n", NULL); } // Put it all together. const char pattern[] = "=head1 NAME\n" "\n" - "%s - %s\n" - "\n" - "%s\n" - "\n" + "%s - %s" + "%s" "=head1 DESCRIPTION\n" "\n" - "%s\n" - "\n" - "%s\n" - "\n" - "%s\n" - "\n" - "%s\n" - "\n" + "%s" + "%s" + "%s" + "%s" "=cut\n" "\n"; char *pod http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/src/CFCPerlPod.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.c b/compiler/src/CFCPerlPod.c index 329142b..08d8bda 100644 --- a/compiler/src/CFCPerlPod.c +++ b/compiler/src/CFCPerlPod.c @@ -16,6 +16,9 @@ #include <string.h> #include <ctype.h> + +#include <cmark.h> + #define CFC_NEED_BASE_STRUCT_DEF #include "CFCBase.h" #include "CFCPerlPod.h" @@ -26,6 +29,7 @@ #include "CFCParamList.h" #include "CFCFunction.h" #include "CFCDocuComment.h" +#include "CFCUri.h" #ifndef true #define true 1 @@ -55,6 +59,21 @@ static const CFCMeta CFCPERLPOD_META = { (CFCBase_destroy_t)CFCPerlPod_destroy }; +static char* +S_nodes_to_pod(CFCClass *klass, cmark_node *node); + +static char* +S_pod_escape(const char *content); + +static char* +S_convert_link(CFCClass *klass, cmark_node *link); + +static char* +S_pod_link(const char *text, const char *name); + +static char* +S_perlify_pod(const char *source); + CFCPerlPod* CFCPerlPod_new(void) { CFCPerlPod *self @@ -161,16 +180,14 @@ CFCPerlPod_methods_pod(CFCPerlPod *self, CFCClass *klass) { } char *meth_pod; if (meth_spec.pod) { - meth_pod = CFCPerlPod_perlify_doc_text(self, meth_spec.pod); + meth_pod = CFCUtil_sprintf("%s\n", meth_spec.pod); } else { - char *raw + meth_pod = CFCPerlPod_gen_subroutine_pod(self, (CFCFunction*)method, meth_spec.alias, klass, meth_spec.sample, class_name, false); - meth_pod = CFCPerlPod_perlify_doc_text(self, raw); - FREEMEM(raw); } if (CFCMethod_abstract(method)) { abstract_pod = CFCUtil_cat(abstract_pod, meth_pod, NULL); @@ -204,19 +221,15 @@ CFCPerlPod_constructors_pod(CFCPerlPod *self, CFCClass *klass) { for (size_t i = 0; i < self->num_constructors; i++) { NamePod slot = self->constructors[i]; if (slot.pod) { - char *perlified = CFCPerlPod_perlify_doc_text(self, slot.pod); - pod = CFCUtil_cat(pod, perlified, NULL); - FREEMEM(perlified); + pod = CFCUtil_cat(pod, slot.pod, "\n", NULL); } else { CFCFunction *init_func = CFCClass_function(klass, slot.func); char *sub_pod = CFCPerlPod_gen_subroutine_pod(self, init_func, slot.alias, klass, slot.sample, class_name, true); - char *perlified = CFCPerlPod_perlify_doc_text(self, sub_pod); - pod = CFCUtil_cat(pod, perlified, NULL); + pod = CFCUtil_cat(pod, sub_pod, NULL); FREEMEM(sub_pod); - FREEMEM(perlified); } } return pod; @@ -277,8 +290,8 @@ CFCPerlPod_gen_subroutine_pod(CFCPerlPod *self, CFCFunction *func, // Incorporate "description" text from DocuComment. const char *long_doc = CFCDocuComment_get_description(docucomment); if (long_doc && strlen(long_doc)) { - char *perlified = CFCPerlPod_perlify_doc_text(self, long_doc); - pod = CFCUtil_cat(pod, perlified, "\n\n", NULL); + char *perlified = CFCPerlPod_md_to_pod(self, klass, long_doc); + pod = CFCUtil_cat(pod, perlified, NULL); FREEMEM(perlified); } @@ -288,8 +301,10 @@ CFCPerlPod_gen_subroutine_pod(CFCPerlPod *self, CFCFunction *func, if (param_names[0]) { pod = CFCUtil_cat(pod, "=over\n\n", NULL); for (size_t i = 0; param_names[i] != NULL; i++) { + char *perlified = CFCPerlPod_md_to_pod(self, klass, param_docs[i]); pod = CFCUtil_cat(pod, "=item *\n\nB<", param_names[i], "> - ", - param_docs[i], "\n\n", NULL); + perlified, NULL); + FREEMEM(perlified); } pod = CFCUtil_cat(pod, "=back\n\n", NULL); } @@ -297,54 +312,374 @@ CFCPerlPod_gen_subroutine_pod(CFCPerlPod *self, CFCFunction *func, // Add return value description, if any. const char *retval_doc = CFCDocuComment_get_retval(docucomment); if (retval_doc && strlen(retval_doc)) { - pod = CFCUtil_cat(pod, "Returns: ", retval_doc, "\n\n", NULL); + char *perlified = CFCPerlPod_md_to_pod(self, klass, retval_doc); + pod = CFCUtil_cat(pod, "Returns: ", perlified, NULL); + FREEMEM(perlified); } return pod; } char* -CFCPerlPod_perlify_doc_text(CFCPerlPod *self, const char *source) { +CFCPerlPod_md_to_pod(CFCPerlPod *self, CFCClass *klass, const char *md) { (void)self; // unused - // Change <code>foo</code> to C<< foo >>. - char *copy = CFCUtil_strdup(source); - char *orig = copy; - copy = CFCUtil_global_replace(orig, "<code>", "C<< "); - FREEMEM(orig); - orig = copy; - copy = CFCUtil_global_replace(orig, "</code>", " >>"); - FREEMEM(orig); - - // Lowercase all method names: Open_In() => open_in() - for (size_t i = 0, max = strlen(copy); i < max; i++) { - if (isupper(copy[i])) { - size_t mark = i; - for (; i < max; i++) { - char c = copy[i]; - if (!(isalpha(c) || c == '_')) { - if (memcmp(copy + i, "()", 2) == 0) { - for (size_t j = mark; j < i; j++) { - copy[j] = tolower(copy[j]); - } - i += 2; // go past parens. - } - break; + cmark_node *doc = cmark_parse_document(md, strlen(md)); + char *pod = S_nodes_to_pod(klass, doc); + cmark_node_free(doc); + char *perlified = S_perlify_pod(pod); + + FREEMEM(pod); + return perlified; +} + +static char* +S_nodes_to_pod(CFCClass *klass, cmark_node *node) { + char *result = CFCUtil_strdup(""); + + while (node) { + cmark_node_type type = cmark_node_get_type(node); + + switch (type) { + case NODE_DOCUMENT: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, children_pod, NULL); + FREEMEM(children_pod); + break; + } + + case NODE_PARAGRAPH: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, children_pod, "\n\n", NULL); + FREEMEM(children_pod); + break; + } + + case NODE_BLOCK_QUOTE: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, "=over\n\n", children_pod, + "\n=back\n\n", NULL); + FREEMEM(children_pod); + break; + } + + case NODE_LIST_ITEM: { + // TODO: Ordered lists. + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, "=item *\n\n", children_pod, + NULL); + FREEMEM(children_pod); + break; + } + + case NODE_LIST: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, "=over\n\n", children_pod, + "=back\n\n", NULL); + FREEMEM(children_pod); + break; + } + + case NODE_HEADER: { + cmark_node *child = cmark_node_first_child(node); + int header_level = cmark_node_get_header_level(node); + char *children_pod = S_nodes_to_pod(klass, child); + char *header = CFCUtil_sprintf("=head%d %s\n\n", + header_level + 2, children_pod); + result = CFCUtil_cat(result, header, NULL); + FREEMEM(header); + FREEMEM(children_pod); + break; + } + + case NODE_CODE_BLOCK: { + const char *content = cmark_node_get_string_content(node); + char *escaped = S_pod_escape(content); + // Chomp trailing newline. + size_t len = strlen(escaped); + if (len > 0 && escaped[len-1] == '\n') { + escaped[len-1] = '\0'; } + char *indented + = CFCUtil_global_replace(escaped, "\n", "\n "); + result = CFCUtil_cat(result, " ", indented, "\n\n", NULL); + FREEMEM(indented); + FREEMEM(escaped); + break; + } + + case NODE_HTML: { + const char *html = cmark_node_get_string_content(node); + result = CFCUtil_cat(result, "=begin html\n\n", html, + "\n=end\n\n", NULL); + break; } + + case NODE_HRULE: + break; + + case NODE_REFERENCE_DEF: + break; + + case NODE_TEXT: { + const char *content = cmark_node_get_string_content(node); + char *escaped = S_pod_escape(content); + result = CFCUtil_cat(result, escaped, NULL); + FREEMEM(escaped); + break; + } + + case NODE_LINEBREAK: + // POD doesn't support line breaks. Start a new paragraph. + result = CFCUtil_cat(result, "\n\n", NULL); + break; + + case NODE_SOFTBREAK: + result = CFCUtil_cat(result, "\n", NULL); + break; + + case NODE_INLINE_CODE: { + const char *content = cmark_node_get_string_content(node); + char *escaped = S_pod_escape(content); + result = CFCUtil_cat(result, "C<", escaped, ">", NULL); + FREEMEM(escaped); + break; + } + + case NODE_INLINE_HTML: { + const char *html = cmark_node_get_string_content(node); + CFCUtil_warn("Inline HTML not supported in POD: %s", html); + break; + } + + case NODE_LINK: { + char *pod = S_convert_link(klass, node); + result = CFCUtil_cat(result, pod, NULL); + FREEMEM(pod); + break; + } + + case NODE_IMAGE: + CFCUtil_warn("Images not supported in POD"); + break; + + case NODE_STRONG: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, "B<", children_pod, ">", NULL); + FREEMEM(children_pod); + break; + } + + case NODE_EMPH: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(klass, child); + result = CFCUtil_cat(result, "I<", children_pod, ">", NULL); + FREEMEM(children_pod); + break; + } + + default: + CFCUtil_die("Invalid cmark node type: %d", type); + break; } + + node = cmark_node_next(node); } - // Change all instances of NULL to 'undef' - orig = copy; - copy = CFCUtil_global_replace(orig, "NULL", "undef"); - FREEMEM(orig); + return result; +} + +static char* +S_pod_escape(const char *content) { + size_t len = strlen(content); + size_t result_len = 0; + size_t result_cap = len + 256; + char *result = (char*)MALLOCATE(result_cap + 1); + + for (size_t i = 0; i < len; i++) { + const char *subst = content + i; + size_t subst_size = 1; + + switch (content[i]) { + case '<': + // Escape "less than". + subst = "E<lt>"; + subst_size = 5; + break; + case '>': + // Escape "greater than". + subst = "E<gt>"; + subst_size = 5; + break; + case '|': + // Escape vertical bar. + subst = "E<verbar>"; + subst_size = 9; + break; + case '=': + // Escape equal sign at start of line. + if (i == 0 || content[i-1] == '\n') { + subst = "E<61>"; + subst_size = 5; + } + break; + default: + break; + } + + if (result_len + subst_size > result_cap) { + result_cap += 256; + result = (char*)REALLOCATE(result, result_cap + 1); + } + + memcpy(result + result_len, subst, subst_size); + result_len += subst_size; + } + + result[result_len] = '\0'; + + return result; +} + +static char* +S_convert_link(CFCClass *klass, cmark_node *link) { + cmark_node *child = cmark_node_first_child(link); + const char *uri = cmark_node_get_url(link); + char *text = S_nodes_to_pod(klass, child); + char *retval; + + if (!CFCUri_is_clownfish_uri(uri)) { + retval = S_pod_link(text, uri); + FREEMEM(text); + return retval; + } + + char *new_uri = NULL; + char *new_text = NULL; + CFCUri *uri_obj = CFCUri_new(uri, klass); + int type = CFCUri_get_type(uri_obj); + + switch (type) { + case CFC_URI_NULL: + // Change all instances of NULL to 'undef' + new_text = CFCUtil_strdup("undef"); + break; + + case CFC_URI_CLASS: { + const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj); + CFCClass *uri_class + = CFCClass_fetch_by_struct_sym(full_struct_sym); - // Change "Err_error" to "Clownfish->error". - orig = copy; - copy = CFCUtil_global_replace(orig, "Err_error", "Clownfish->error"); - FREEMEM(orig); + if (!uri_class) { + CFCUtil_warn("URI class not found: %s", full_struct_sym); + } + else if (uri_class != klass) { + const char *class_name = CFCClass_get_class_name(uri_class); + new_uri = CFCUtil_strdup(class_name); + } + + if (text[0] != '\0') { + // Keep text. + break; + } + + if (strcmp(CFCUri_get_prefix(uri_obj), + CFCClass_get_prefix(klass)) == 0 + ) { + // Same parcel. + const char *struct_sym = CFCUri_get_struct_sym(uri_obj); + new_text = CFCUtil_strdup(struct_sym); + } + else { + // Other parcel. + if (!uri_class) { + new_text = CFCUtil_strdup(full_struct_sym); + } + else { + const char *class_name + = CFCClass_get_class_name(uri_class); + new_text = CFCUtil_strdup(class_name); + } + } + + break; + } + + case CFC_URI_FUNCTION: + case CFC_URI_METHOD: { + const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj); + const char *func_sym = CFCUri_get_func_sym(uri_obj); + + // Convert "Err_get_error" to "Clownfish->error". + if (strcmp(full_struct_sym, "cfish_Err") == 0 + && strcmp(func_sym, "get_error") == 0 + ) { + new_text = CFCUtil_strdup("Clownfish->error"); + break; + } - return copy; + CFCClass *uri_class + = CFCClass_fetch_by_struct_sym(full_struct_sym); + + // TODO: Link to relevant POD section. This isn't easy because + // the section headers for functions also contain a description + // of the parameters. + + if (!uri_class) { + CFCUtil_warn("URI class not found: %s", full_struct_sym); + } + else if (uri_class != klass) { + const char *class_name = CFCClass_get_class_name(uri_class); + new_uri = CFCUtil_strdup(class_name); + } + + new_text = CFCUtil_sprintf("%s()", func_sym); + for (size_t i = 0; new_text[i] != '\0'; ++i) { + new_text[i] = tolower(new_text[i]); + } + + break; + } + } + + if (new_text) { + FREEMEM(text); + text = new_text; + } + + if (new_uri) { + retval = S_pod_link(text, new_uri); + FREEMEM(new_uri); + FREEMEM(text); + } + else { + retval = text; + } + + CFCBase_decref((CFCBase*)uri_obj); + + return retval; +} + +static char* +S_pod_link(const char *text, const char *name) { + if (!text || text[0] == '\0' || strcmp(text, name) == 0) { + return CFCUtil_sprintf("L<%s>", name); + } + else { + return CFCUtil_sprintf("L<%s|%s>", text, name); + } +} + +static char* +S_perlify_pod(const char *source) { + // Change all instances of NULL to 'undef' + return CFCUtil_global_replace(source, "NULL", "undef"); } http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c2a78e23/compiler/src/CFCPerlPod.h ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.h b/compiler/src/CFCPerlPod.h index 3fb94eb..fe10faf 100644 --- a/compiler/src/CFCPerlPod.h +++ b/compiler/src/CFCPerlPod.h @@ -99,7 +99,8 @@ const char* CFCPerlPod_get_description(CFCPerlPod *self); char* -CFCPerlPod_perlify_doc_text(CFCPerlPod *self, const char *source); +CFCPerlPod_md_to_pod(CFCPerlPod *self, struct CFCClass *klass, + const char *source); /** Autogenerate pod for either a Clownfish::CFC::Model::Method or a * Clownfish::CFC::Model::Function.
