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/f57df2fc Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/f57df2fc Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/f57df2fc Branch: refs/heads/markdown Commit: f57df2fc21e610e82a39a6d37f85b327f8bf420b Parents: 12d994b Author: Nick Wellnhofer <[email protected]> Authored: Mon Nov 10 17:35:22 2014 +0100 Committer: Nick Wellnhofer <[email protected]> Committed: Sun Nov 23 18:35:49 2014 +0100 ---------------------------------------------------------------------- compiler/perl/lib/Clownfish/CFC.xs | 4 +- compiler/src/CFCClass.c | 8 + compiler/src/CFCClass.h | 8 + compiler/src/CFCPerlClass.c | 34 ++-- compiler/src/CFCPerlPod.c | 307 ++++++++++++++++++++++++++++++-- compiler/src/CFCPerlPod.h | 2 +- 6 files changed, 327 insertions(+), 36 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/f57df2fc/compiler/perl/lib/Clownfish/CFC.xs ---------------------------------------------------------------------- diff --git a/compiler/perl/lib/Clownfish/CFC.xs b/compiler/perl/lib/Clownfish/CFC.xs index a6ea249..1732653 100644 --- a/compiler/perl/lib/Clownfish/CFC.xs +++ b/compiler/perl/lib/Clownfish/CFC.xs @@ -2463,11 +2463,11 @@ PPCODE: SV* -_perlify_doc_text(self, source) +_md_to_pod(self, source) CFCPerlPod *self; 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, source)); OUTPUT: RETVAL SV* http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/f57df2fc/compiler/src/CFCClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCClass.c b/compiler/src/CFCClass.c index 59ec8c3..6a23183 100644 --- a/compiler/src/CFCClass.c +++ b/compiler/src/CFCClass.c @@ -314,6 +314,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/f57df2fc/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/f57df2fc/compiler/src/CFCPerlClass.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlClass.c b/compiler/src/CFCPerlClass.c index aa3d738..57561c3 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, 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, 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/f57df2fc/compiler/src/CFCPerlPod.c ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.c b/compiler/src/CFCPerlPod.c index b864fcb..28b98db 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" @@ -55,6 +58,18 @@ static const CFCMeta CFCPERLPOD_META = { (CFCBase_destroy_t)CFCPerlPod_destroy }; +static char* +S_nodes_to_pod(cmark_node *node); + +static char* +S_pod_escape(const char *content); + +static char* +S_convert_uri(const char *uri); + +static char* +S_perlify_pod(const char *source); + CFCPerlPod* CFCPerlPod_new(void) { CFCPerlPod *self @@ -161,16 +176,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 +217,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; @@ -318,8 +327,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, long_doc); + pod = CFCUtil_cat(pod, perlified, NULL); FREEMEM(perlified); } @@ -329,8 +338,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, 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); } @@ -338,16 +349,282 @@ 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, 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, const char *md) { (void)self; // unused + cmark_node *doc = cmark_parse_document(md, strlen(md)); + char *pod = S_nodes_to_pod(doc); + cmark_node_destroy(doc); + char *perlified = S_perlify_pod(pod); + + FREEMEM(pod); + return perlified; +} + +static char* +S_nodes_to_pod(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(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(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(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(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(child); + result = CFCUtil_cat(result, "=over\n\n", children_pod, + "=back\n\n", NULL); + FREEMEM(children_pod); + break; + } + + case NODE_ATX_HEADER: + case NODE_SETEXT_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(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_INDENTED_CODE: + case NODE_FENCED_CODE: { + 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 = S_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_STRING: { + 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: { + cmark_node *child = cmark_node_first_child(node); + char *children_pod = S_nodes_to_pod(child); + char *uri = S_convert_uri(cmark_node_get_url(node)); + if (children_pod[0] == '\0' + || strcmp(children_pod, uri) == 0 + ) { + result = CFCUtil_cat(result, "L<", uri, ">", NULL); + } + else { + result = CFCUtil_cat(result, "L<", children_pod, "|", uri, + ">", NULL); + } + FREEMEM(children_pod); + FREEMEM(uri); + 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(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(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_uri(const char *uri) { + if (memcmp(uri, "clownfish:", 10) != 0) { + return CFCUtil_strdup(uri); + } + if (memcmp(uri, "clownfish:class:", 16) != 0) { + CFCUtil_die("Invalid clownfish URI: %s", uri); + } + + // Convert 'clownfish:class:{parcel}:{name}' to '{class_name}' + const char *src = uri + 16; + const char *colon = strchr(src, ':'); + if (!colon) { + CFCUtil_die("Invalid clownfish URI: %s", uri); + } + char *struct_sym = CFCUtil_strdup(src); + struct_sym[colon-src] = '_'; + CFCClass *klass = CFCClass_fetch_by_struct_sym(struct_sym); + FREEMEM(struct_sym); + if (!klass) { + CFCUtil_warn("No class found for URI %s\n", uri); + return CFCUtil_strdup(uri); + } + + return CFCUtil_strdup(CFCClass_get_class_name(klass)); +} + +static char* +S_perlify_pod(const char *source) { // Change `foo` to C<< foo >>. char *copy = CFCUtil_strdup(source); char *orig = copy; http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/f57df2fc/compiler/src/CFCPerlPod.h ---------------------------------------------------------------------- diff --git a/compiler/src/CFCPerlPod.h b/compiler/src/CFCPerlPod.h index 38bb0c1..1ebac1a 100644 --- a/compiler/src/CFCPerlPod.h +++ b/compiler/src/CFCPerlPod.h @@ -99,7 +99,7 @@ const char* CFCPerlPod_get_description(CFCPerlPod *self); char* -CFCPerlPod_perlify_doc_text(CFCPerlPod *self, const char *source); +CFCPerlPod_md_to_pod(CFCPerlPod *self, const char *source); /** Autogenerate pod for either a Clownfish::CFC::Model::Method or a * Clownfish::CFC::Model::Function.
