static void render_html(test_batch_runner *runner) { char *html; static const char markdown[] = "foo *bar*\n" "\n" "paragraph 2\n"; cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); cmark_node *paragraph = cmark_node_first_child(doc); html = cmark_render_html(paragraph, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<p>foo <em>bar</em></p>\n", "render single paragraph"); free(html); cmark_node *string = cmark_node_first_child(paragraph); html = cmark_render_html(string, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "foo ", "render single inline"); free(html); cmark_node *emph = cmark_node_next(string); html = cmark_render_html(emph, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<em>bar</em>", "render inline with children"); free(html); cmark_node_free(doc); }
static void md_block_external_images(cmark_iter *const iter) { for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_EXIT != event) continue; cmark_node *const node = cmark_iter_get_node(iter); if(CMARK_NODE_IMAGE != cmark_node_get_type(node)) continue; char const *const URI = cmark_node_get_url(node); if(URI) { if(0 == strncasecmp(URI, STR_LEN("hash:"))) continue; if(0 == strncasecmp(URI, STR_LEN("data:"))) continue; } cmark_node *link = cmark_node_new(CMARK_NODE_LINK); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_url(link, URI); for(;;) { cmark_node *child = cmark_node_first_child(node); if(!child) break; cmark_node_append_child(link, child); } if(cmark_node_first_child(link)) { cmark_node_set_literal(text, " (external image)"); } else { cmark_node_set_literal(text, "(external image)"); } cmark_node_append_child(link, text); cmark_node_insert_before(node, link); cmark_node_free(node); } }
char* CFCPerlPod_md_doc_to_pod(const char *module, const char *md) { int options = CMARK_OPT_SMART | CMARK_OPT_VALIDATE_UTF8; cmark_node *doc = cmark_parse_document(md, strlen(md), options); cmark_node *maybe_header = cmark_node_first_child(doc); char *name; char *desc; if (maybe_header && cmark_node_get_type(maybe_header) == CMARK_NODE_HEADER ) { cmark_node *header_child = cmark_node_first_child(maybe_header); char *short_desc = S_nodes_to_pod(header_child, NULL, 1); name = CFCUtil_sprintf("%s - %s", module, short_desc); FREEMEM(short_desc); cmark_node *remaining = cmark_node_next(maybe_header); desc = S_nodes_to_pod(remaining, NULL, 1); } else { // No header found. name = CFCUtil_strdup(module); desc = S_node_to_pod(doc, NULL, 1); } const char *pattern = "=head1 NAME\n" "\n" "%s\n" "\n" "=head1 DESCRIPTION\n" "\n" "%s"; char *retval = CFCUtil_sprintf(pattern, name, desc); FREEMEM(name); FREEMEM(desc); cmark_node_free(doc); return retval; }
static char* S_convert_link(cmark_node *link, CFCClass *doc_class, int header_level) { cmark_node *child = cmark_node_first_child(link); const char *uri = cmark_node_get_url(link); char *text = S_nodes_to_pod(child, doc_class, header_level); 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, doc_class); CFCUriType type = CFCUri_get_type(uri_obj); switch (type) { case CFC_URI_ERROR: { const char *error = CFCUri_get_error(uri_obj); new_text = CFCUtil_sprintf("[%s]", error); break; } case CFC_URI_NULL: // Change all instances of NULL to 'undef' new_text = CFCUtil_strdup("undef"); break; case CFC_URI_CLASS: { CFCClass *klass = CFCUri_get_class(uri_obj); if (klass != doc_class) { const char *class_name = CFCClass_get_name(klass); new_uri = CFCUtil_strdup(class_name); } if (text[0] == '\0') { const char *src = CFCClass_included(klass) ? CFCClass_get_name(klass) : CFCClass_get_struct_sym(klass); new_text = CFCUtil_strdup(src); } 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); // Convert "Err_get_error" to "Clownfish->error". if (strcmp(CFCClass_full_struct_sym(klass), "cfish_Err") == 0 && strcmp(name, "get_error") == 0 ) { new_text = CFCUtil_strdup("Clownfish->error"); break; } char *perl_name = CFCUtil_strdup(name); for (size_t i = 0; perl_name[i] != '\0'; ++i) { perl_name[i] = CFCUtil_tolower(perl_name[i]); } // The Perl POD only contains sections for novel methods. Link // to the class where the method is declared first. if (type == CFC_URI_METHOD) { CFCClass *parent = CFCClass_get_parent(klass); while (parent && CFCClass_method(parent, name)) { klass = parent; parent = CFCClass_get_parent(klass); } } if (klass == doc_class) { new_uri = CFCUtil_sprintf("/%s", perl_name); } else { const char *class_name = CFCClass_get_name(klass); new_uri = CFCUtil_sprintf("%s/%s", class_name, perl_name); } if (text[0] == '\0') { new_text = CFCUtil_sprintf("%s()", perl_name); } FREEMEM(perl_name); break; } case CFC_URI_DOCUMENT: { CFCDocument *doc = CFCUri_get_document(uri_obj); const char *path_part = CFCDocument_get_path_part(doc); new_uri = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "::"); if (text[0] == '\0') { const char *name = CFCDocument_get_name(doc); new_text = CFCUtil_strdup(name); } 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_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); 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; } 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 void create_tree(test_batch_runner *runner) { char *html; cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH); OK(runner, !cmark_node_insert_before(doc, p), "insert before root fails"); OK(runner, !cmark_node_insert_after(doc, p), "insert after root fails"); OK(runner, cmark_node_append_child(doc, p), "append1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append1 consistent"); OK(runner, cmark_node_parent(p) == doc, "node_parent"); cmark_node *emph = cmark_node_new(CMARK_NODE_EMPH); OK(runner, cmark_node_prepend_child(p, emph), "prepend1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend1 consistent"); cmark_node *str1 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str1, "Hello, "); OK(runner, cmark_node_prepend_child(p, str1), "prepend2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend2 consistent"); cmark_node *str3 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str3, "!"); OK(runner, cmark_node_append_child(p, str3), "append2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append2 consistent"); cmark_node *str2 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str2, "world"); OK(runner, cmark_node_append_child(emph, str2), "append3"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append3 consistent"); html = cmark_render_html(doc, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<p>Hello, <em>world</em>!</p>\n", "render_html"); free(html); OK(runner, cmark_node_insert_before(str1, str3), "ins before1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before1 consistent"); // 31e OK(runner, cmark_node_first_child(p) == str3, "ins before1 works"); OK(runner, cmark_node_insert_before(str1, emph), "ins before2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before2 consistent"); // 3e1 OK(runner, cmark_node_last_child(p) == str1, "ins before2 works"); OK(runner, cmark_node_insert_after(str1, str3), "ins after1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after1 consistent"); // e13 OK(runner, cmark_node_next(str1) == str3, "ins after1 works"); OK(runner, cmark_node_insert_after(str1, emph), "ins after2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after2 consistent"); // 1e3 OK(runner, cmark_node_previous(emph) == str1, "ins after2 works"); cmark_node_unlink(emph); html = cmark_render_html(doc, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<p>Hello, !</p>\n", "render_html after shuffling"); free(html); cmark_node_free(doc); // TODO: Test that the contents of an unlinked inline are valid // after the parent block was destroyed. This doesn't work so far. cmark_node_free(emph); }
static void accessors(test_batch_runner *runner) { static const char markdown[] = "## Header\n" "\n" "* Item 1\n" "* Item 2\n" "\n" "2. Item 1\n" "\n" "3. Item 2\n" "\n" "\n" " code\n" "\n" "``` lang\n" "fenced\n" "```\n" "\n" "<div>html</div>\n" "\n" "[link](url 'title')\n"; cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); // Getters cmark_node *header = cmark_node_first_child(doc); INT_EQ(runner, cmark_node_get_header_level(header), 2, "get_header_level"); cmark_node *bullet_list = cmark_node_next(header); INT_EQ(runner, cmark_node_get_list_type(bullet_list), CMARK_BULLET_LIST, "get_list_type bullet"); INT_EQ(runner, cmark_node_get_list_tight(bullet_list), 1, "get_list_tight tight"); cmark_node *ordered_list = cmark_node_next(bullet_list); INT_EQ(runner, cmark_node_get_list_type(ordered_list), CMARK_ORDERED_LIST, "get_list_type ordered"); INT_EQ(runner, cmark_node_get_list_delim(ordered_list), CMARK_PERIOD_DELIM, "get_list_delim ordered"); INT_EQ(runner, cmark_node_get_list_start(ordered_list), 2, "get_list_start"); INT_EQ(runner, cmark_node_get_list_tight(ordered_list), 0, "get_list_tight loose"); cmark_node *code = cmark_node_next(ordered_list); STR_EQ(runner, cmark_node_get_literal(code), "code\n", "get_literal indented code"); cmark_node *fenced = cmark_node_next(code); STR_EQ(runner, cmark_node_get_literal(fenced), "fenced\n", "get_literal fenced code"); STR_EQ(runner, cmark_node_get_fence_info(fenced), "lang", "get_fence_info"); cmark_node *html = cmark_node_next(fenced); STR_EQ(runner, cmark_node_get_literal(html), "<div>html</div>\n", "get_literal html"); cmark_node *paragraph = cmark_node_next(html); INT_EQ(runner, cmark_node_get_start_line(paragraph), 19, "get_start_line"); INT_EQ(runner, cmark_node_get_start_column(paragraph), 1, "get_start_column"); INT_EQ(runner, cmark_node_get_end_line(paragraph), 19, "get_end_line"); cmark_node *link = cmark_node_first_child(paragraph); STR_EQ(runner, cmark_node_get_url(link), "url", "get_url"); STR_EQ(runner, cmark_node_get_title(link), "title", "get_title"); cmark_node *string = cmark_node_first_child(link); STR_EQ(runner, cmark_node_get_literal(string), "link", "get_literal string"); // Setters OK(runner, cmark_node_set_header_level(header, 3), "set_header_level"); OK(runner, cmark_node_set_list_type(bullet_list, CMARK_ORDERED_LIST), "set_list_type ordered"); OK(runner, cmark_node_set_list_delim(bullet_list, CMARK_PAREN_DELIM), "set_list_delim paren"); OK(runner, cmark_node_set_list_start(bullet_list, 3), "set_list_start"); OK(runner, cmark_node_set_list_tight(bullet_list, 0), "set_list_tight loose"); OK(runner, cmark_node_set_list_type(ordered_list, CMARK_BULLET_LIST), "set_list_type bullet"); OK(runner, cmark_node_set_list_tight(ordered_list, 1), "set_list_tight tight"); OK(runner, cmark_node_set_literal(code, "CODE\n"), "set_literal indented code"); OK(runner, cmark_node_set_literal(fenced, "FENCED\n"), "set_literal fenced code"); OK(runner, cmark_node_set_fence_info(fenced, "LANG"), "set_fence_info"); OK(runner, cmark_node_set_literal(html, "<div>HTML</div>\n"), "set_literal html"); OK(runner, cmark_node_set_url(link, "URL"), "set_url"); OK(runner, cmark_node_set_title(link, "TITLE"), "set_title"); OK(runner, cmark_node_set_literal(string, "LINK"), "set_literal string"); char *rendered_html = cmark_render_html(doc, CMARK_OPT_DEFAULT); static const char expected_html[] = "<h3>Header</h3>\n" "<ol start=\"3\">\n" "<li>\n" "<p>Item 1</p>\n" "</li>\n" "<li>\n" "<p>Item 2</p>\n" "</li>\n" "</ol>\n" "<ul>\n" "<li>Item 1</li>\n" "<li>Item 2</li>\n" "</ul>\n" "<pre><code>CODE\n" "</code></pre>\n" "<pre><code class=\"language-LANG\">FENCED\n" "</code></pre>\n" "<div>HTML</div>\n" "<p><a href=\"URL\" title=\"TITLE\">LINK</a></p>\n"; STR_EQ(runner, rendered_html, expected_html, "setters work"); free(rendered_html); // Getter errors INT_EQ(runner, cmark_node_get_header_level(bullet_list), 0, "get_header_level error"); INT_EQ(runner, cmark_node_get_list_type(header), CMARK_NO_LIST, "get_list_type error"); INT_EQ(runner, cmark_node_get_list_start(code), 0, "get_list_start error"); INT_EQ(runner, cmark_node_get_list_tight(fenced), 0, "get_list_tight error"); OK(runner, cmark_node_get_literal(ordered_list) == NULL, "get_literal error"); OK(runner, cmark_node_get_fence_info(paragraph) == NULL, "get_fence_info error"); OK(runner, cmark_node_get_url(html) == NULL, "get_url error"); OK(runner, cmark_node_get_title(header) == NULL, "get_title error"); // Setter errors OK(runner, !cmark_node_set_header_level(bullet_list, 3), "set_header_level error"); OK(runner, !cmark_node_set_list_type(header, CMARK_ORDERED_LIST), "set_list_type error"); OK(runner, !cmark_node_set_list_start(code, 3), "set_list_start error"); OK(runner, !cmark_node_set_list_tight(fenced, 0), "set_list_tight error"); OK(runner, !cmark_node_set_literal(ordered_list, "content\n"), "set_literal error"); OK(runner, !cmark_node_set_fence_info(paragraph, "lang"), "set_fence_info error"); OK(runner, !cmark_node_set_url(html, "url"), "set_url error"); OK(runner, !cmark_node_set_title(header, "title"), "set_title error"); OK(runner, !cmark_node_set_header_level(header, 0), "set_header_level too small"); OK(runner, !cmark_node_set_header_level(header, 7), "set_header_level too large"); OK(runner, !cmark_node_set_list_type(bullet_list, CMARK_NO_LIST), "set_list_type invalid"); OK(runner, !cmark_node_set_list_start(bullet_list, -1), "set_list_start negative"); cmark_node_free(doc); }