#include "creole.h" #include #include #define COUNT(arr) (sizeof(arr)/sizeof((arr)[0])) #define strneq(a, b, n) (strncmp(a, b, n) == 0) struct { const char *name, *input, *output; } tests[] = { { .name = "Empty input produces no output", .input = "", .output = "" }, { .name = "Basic paragraph markup", .input = "Basic paragraph test with <, >, & and \"", .output = "

Basic paragraph test with <, >, & and "

" }, { .name = "Two paragraphs next to each other.", .input = "Hello,\n\nworld!", .output = "

Hello,

world!

" }, { .name = "h1", .input = "= Header =", .output = "

Header

" }, { .name = "h2", .input = "== Header =", .output = "

Header

" }, { .name = "h3", .input = "=== Header =", .output = "

Header

" }, { .name = "h4", .input = "==== Header =", .output = "

Header

" }, { .name = "h5", .input = "===== Header", .output = "
Header
" }, { .name = "h6", .input = "====== Header =", .output = "
Header
" }, { .name = ">h6", .input = "======= Header =", .output = "

======= Header =

" }, { .name = "Unnamed link", .input = "[[MyPage]]", .output = "

MyPage

" }, { .name = "Named link", .input = "[[MyPage|My page]]", .output = "

My page

" }, { .name = "Escaped link", .input = "A paragraph with an ~[[escaped link]].", .output = "

A paragraph with an [[escaped link]].

" }, { .name = "Link with an escaped end", .input = "[[https://example.com|A link with an escaped ~]] end]]", .output = "

A link with an escaped ]] end

" }, { .name = "Link with empty text", .input = "[[https://example.com|]]", .output = "

" }, { .name = "Link with empty address", .input = "[[|Hello]]", .output = "

Hello

" }, { .name = "Empty link", .input = "[[]]", .output = "

" }, { .name = "Raw HTTP URL", .input = "Here is a http://example.com/examplepage link.", .output = "

Here is a " "http://example.com/examplepage link.

" }, { // This is interesting because it doesn't contain a "://". .name = "Raw mailto URL", .input = "mailto:quandale@dingle.com", .output = "

" "mailto:quandale@dingle.com

" }, { // This test captures a non-standard (?) special case in the parser. .name = "Raw URL followed by full stop", .input = "My favorite website is https://wiki.c2.com/.", .output = "

My favorite website is " "https://wiki.c2.com/.

" }, { .name = "Unnamed URL", .input = "[[http //example.com/examplepage]]", .output = "

" "http //example.com/examplepage

" }, { .name = "Named URL", .input = "[[http //example.com/examplepage|Example Page]]", .output = "

" "Example Page

" }, #if 0 { .name = "Simple unordered list", .input = "* list item\n*list item 2", .output = "" }, { .name = "Simple ordered list", .input = "# list item\n#list item 2", .output = "
  1. list item
  2. \n
  3. list item 2
" }, { .name = "Unordered item with unordered sublist", .input = "* Item\n** Subitem", .output = "" }, { .name = "Unordered sublist without initial tag", .input = "** Sublist item", .output = "

** Sublist item

" }, { .name = "Ordered item with ordered sublist", .input = "# Item\n## Subitem", .output = "
  1. Item
      \n
    1. Subitem
" }, { .name = "Ordered sublist without initial tag", .input = "## Sublist item", .output = "

## Sublist item

" }, { .name = "Unordered item with ordered sublist", .input = "* Item\n*# Subitem", .output = "" }, { .name = "Horizontal rule", .input = "Some text\n----\nSome more text", .output = "

Some text


Some more text

" }, { .name = "Preformatted block", .input = "{{{\nPreformatted block\n}}}", .output = "
Preformatted block\n
" }, { .name = "Two preformatted blocks", .input = "{{{\nPreformatted block\n}}}\n{{{Block 2}}}", .output = "
Preformatted block\n
Block 2
" }, { .name = "Tables", .input = "| A | B |\n| //C// | **D** \\\\ E |", .output = "" "" "
A B
C D
E
" }, { .name = "Image", .input = "{{image.gif|my image}}", .output = "

\"my

" }, { .name = "Inline tt", .input = "Inline {{{tt}}} example {{{here}}}!", .output = "

Inline tt example here!

" }, { .name = "Strong", .input = "**Strong**", .output = "

Strong

" }, { .name = "Emphasis", .input = "//Emphasis//", .output = "

Emphasis

" }, { .name = "Multi-line emphasis", .input = "Bold and italics should //be\nable// to cross lines.\n\n" "But, should //not be...\n\n...able// to cross paragraphs.", .output = "

Bold and italics should be\nable to cross lines.\n

" "

\nBut, should //not be...\n

" "

\n...able// to cross paragraphs.

" }, { .name = "URL/emphasis ambiguity", .input = "This is an //italic// text. This is a url " "http //www.wikicreole.org. This is what can go wrong //this " "should be an italic text//.", .output = "

This is an italic text. This is a url " "" "http //www.wikicreole.org. This is what can go wrong " "this should be an italic text.

" }, { .name = "Difficult emphasis #1", .input = "// http //www.link.org //", .output = "

" "http //www.link.org

" }, { .name = "Difficult emphasis #2", .input = "// http //", .output = "

http

" }, { .name = "Difficult emphasis #3", .input = "// httphpthtpht //", .output = "

httphpthtpht

" }, { .name = "Difficult emphasis #4", .input = "// http //", .output = "

http

" }, { .name = "Difficult emphasis #5", .input = "// http //", .output = "

// http //

" }, { .name = "Difficult emphasis #6", .input = "// http ////", .output = "

http //

" }, { .name = "Difficult emphasis #7", .input = "//httphpthtphtt//", .output = "

httphpthtphtt

" }, { .name = "Difficult emphasis #8", .input = "//http //link.org//", .output = "

" "http //link.org

" }, { .name = "Difficult emphasis #9", .input = "// ftp //www.link.org //", .output = "

" "ftp //www.link.org

" }, { .name = "Difficult emphasis #10", .input = "// ftp //", .output = "

ftp

" }, { .name = "Difficult emphasis #11", .input = "// fttpfptftpft //", .output = "

fttpfptftpft

" }, { .name = "Difficult emphasis #12", .input = "// ftp //", .output = "

ftp

" }, { .name = "Difficult emphasis #13", .input = "// ftp //", .output = "

// ftp //

" }, { .name = "Difficult emphasis #14", .input = "// ftp ////", .output = "

ftp //

" }, { .name = "Difficult emphasis #15", .input = "//fttpfptftpftt//", .output = "

fttpfptftpftt

" }, { .name = "Difficult emphasis #16", .input = "//ftp //link.org//", .output = "

" "ftp //link.org

" } #endif }; int print_escaped(FILE *fp, const char *string, size_t length) { static struct { char from; const char *to; } replacements[] = { { '\t', "\\t" }, { '\n', "\\n" }, { '"', "\\\"" }, }; if (fputc('"', fp) == EOF) { return -1; } for (size_t i = 0; i < length; ++i) { for (size_t j = 0; j < COUNT(replacements); ++j) { if (string[i] == replacements[j].from) { if (fprintf(fp, "%s", replacements[j].to) < 0) { return -1; } goto next_char; } } if (fputc(string[i], fp) == EOF) { return -1; } next_char: ; } if (fputc('"', fp) == EOF) { return -1; } return 0; } int print_escaped_ze(FILE *fp, const char *string) { return print_escaped(fp, string, strlen(string)); } int main(void) { for (size_t i = 0; i < COUNT(tests); ++i) { printf("Running test: \x1b[1m%s\x1b[0m... ", tests[i].name); static char buffer[1024]; FILE *fp = fmemopen(buffer, sizeof(buffer), "wb"); render_creole(fp, tests[i].input, strlen(tests[i].input)); long buffer_length = ftell(fp); fclose(fp); if (!strneq(buffer, tests[i].output, buffer_length)) { printf("\x1b[31merror\x1b[0m\n"); printf("├──── markup: "); print_escaped_ze(stdout, tests[i].input); putchar('\n'); printf("├── expected: "); print_escaped_ze(stdout, tests[i].output); putchar('\n'); printf("└─────── got: "); print_escaped(stdout, buffer, buffer_length); putchar('\n'); } else { printf("\x1b[32mok\x1b[0m\n"); } } }