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/07c8975f Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/07c8975f Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/07c8975f Branch: refs/heads/markdown_v2 Commit: 07c8975f3081e42b08f8a07fb85d4b8e1dc7e881 Parents: b671360 Author: Nick Wellnhofer <[email protected]> Authored: Mon Nov 10 17:35:22 2014 +0100 Committer: Nick Wellnhofer <[email protected]> Committed: Tue Dec 2 18:47:39 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 | 387 ++++++++++++++++++++++++++++---- compiler/src/CFCPerlPod.h | 3 +- 6 files changed, 380 insertions(+), 69 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/07c8975f/compiler/perl/lib/Clownfish/CFC.xs ---------------------------------------------------------------------- diff --git a/compiler/perl/lib/Clownfish/CFC.xs b/compiler/perl/lib/Clownfish/CFC.xs index a6ea249..d13079e 100644 --- a/compiler/perl/lib/Clownfish/CFC.xs +++ b/compiler/perl/lib/Clownfish/CFC.xs @@ -2463,11 +2463,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/07c8975f/compiler/src/CFCClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCClass.c b/compiler/src/CFCClass.c index a64b9ae..cb5660c 100644 --- a/compiler/src/CFCClass.c +++ b/compiler/src/CFCClass.c @@ -309,6 +309,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/07c8975f/compiler/src/CFCClass.h ---------------------------------------------------------------------- diff --git a/compiler/src/CFCClass.h b/compiler/src/CFCClass.h index 877a84a..8d7f4d8 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/07c8975f/compiler/src/CFCPerlClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlClass.c b/compiler/src/CFCPerlClass.c index aa3d738..bb8e4de 100644 --- a/compiler/src/CFCPerlClass.c +++ b/compiler/src/CFCPerlClass.c @@ -361,14 +361,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); @@ -401,27 +405,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/07c8975f/compiler/src/CFCPerlPod.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.c b/compiler/src/CFCPerlPod.c index dd002a6..e28fb55 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,334 @@ 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 `foo` 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. + 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); + } + + 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; + 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' + FREEMEM(text); + text = CFCUtil_strdup("undef"); + break; + + case CFC_URI_CLASS: + case CFC_URI_FUNCTION: + case CFC_URI_METHOD: { + // TODO: Class and method aliases + + const char *struct_sym = CFCUri_full_struct_sym(uri_obj); + CFCClass *uri_class = CFCClass_fetch_by_struct_sym(struct_sym); + if (!uri_class) { + CFCUtil_warn("No class found for URI: %s", uri); + break; + } + + if (uri_class != klass) { + const char *class_name = CFCClass_get_class_name(uri_class); + new_uri = CFCUtil_strdup(class_name); + } + + if (type != CFC_URI_CLASS) { + // TODO: Link to relevant POD section. This isn't easy because + // the section headers for functions also contain a description + // of the parameters. + + FREEMEM(text); + const char *func_sym = CFCUri_get_func_sym(uri_obj); + + if (strcmp(struct_sym, "cfish_Err") == 0 + && strcmp(func_sym, "error") == 0 + ) { + text = CFCUtil_strdup("Clownfish->error"); + } + else { + text = CFCUtil_sprintf("%s()", func_sym); + for (size_t i = 0; text[i] != '\0'; ++i) { + text[i] = tolower(text[i]); } - break; } } + + break; } } - // Change all instances of NULL to 'undef' - orig = copy; - copy = CFCUtil_global_replace(orig, "NULL", "undef"); - FREEMEM(orig); + 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; +} - // Change "Err_error" to "Clownfish->error". - orig = copy; - copy = CFCUtil_global_replace(orig, "Err_error", "Clownfish->error"); - FREEMEM(orig); +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); + } +} - return copy; +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/07c8975f/compiler/src/CFCPerlPod.h ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.h b/compiler/src/CFCPerlPod.h index 38bb0c1..c87c30d 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.
