#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 = "Link with markup in name", .input = "[[https://example.com|a **cool** link]]", .output = "

a cool link

" }, { .name = "Markup in link address is ignored", .input = "[[https://**example**.com/{{wad}}]]", .output = "

https://**example**.com/{{wad}}

" }, { .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 = "Escaped raw URL", .input = "Please don't register ~https://cohost.org/!", .output = "

Please don't register https://cohost.org/!

" }, { .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

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

Emphasis

" }, { .name = "Emphasis spans multiple lines", .input = "Bold and italics should //be\nable// to cross lines.", .output = "

Bold and italics should be\nable to cross lines.

" }, { .name = "Emphasised URL", .input = "//https://example.com//", .output = "

https://example.com

" }, { // I don't know that this is necessarily //correct// behavior... Let's document it anyways .name = "Emphasised URL ending in slash", .input = "//https://example.com///", .output = "

https://example.com/

" }, { .name = "Emphasis does not cross paragraph boundaries", .input = "This text should //not\n\nbe emphased// as it crosses a paragraph boundary.", .output = "

This text should //not

" "

be emphased// as it crosses a paragraph boundary.

" }, { .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 = "Escaped emphasis", .input = "I //love double ~// slashes//!", .output = "

I love double // slashes!

" }, { // Not sure what the standard demands. Here's our behavior. .name = "Empty emphasis", .input = "////", .output = "

////

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

Strong

" }, { // Not sure what the standard demands. Here's our behavior. .name = "Empty bold", .input = "You f****g asshole!", .output = "

You f****g asshole!

" }, { .name = "Escaped bold", .input = "**Strong ~** still strong**", .output = "

Strong ** still strong

" }, { .name = "Nested bold/italic", .input = "//**Strong and emphasized**//", .output = "

Strong and emphasized

" }, { .name = "Alternating bold/italic doesn't work", .input = "//**Strong and emphasized//**", .output = "

**Strong and emphasized**

" }, { .name = "Inline nowiki", .input = "Some examples of markup are: {{{** this **}}}", .output = "

Some examples of markup are: ** <i>this</i> **

" }, { .name = "Inline nowiki is stripped", .input = "{{{ foo }}}", .output = "

foo

" }, { // Spec does not seem to clear this issue. // As it becomes a in their example, I'll assume newlines aren't included verbatim in the output. // However, for consistency with (e.g.) bold text, they will "work" across line boundaries. .name = "Linebreaks in inline nowiki", .input = "This should not work {{{foo\nbar}}}", .output = "

This should not work foo\nbar

" }, { .name = "Inline nowiki brace helll", .input = "Creole: Inline nowiki with closing braces: {{{if (a>b) { b = a; }}}}.", .output = "

Creole: Inline nowiki with closing braces: if (a>b) { b = a; }.

" }, { .name = "Nowiki block", .input = "Here is some stuff:\n\n{{{\nwad\n}}}", .output = "

Here is some stuff:

" "
wad
" }, { .name = "Non-closed nowiki block", .input = "Here is some stuff:\n\n{{{\nwad", .output = "

Here is some stuff:

" "

{{{\nwad

" }, { .name = "Empty nowiki block", .input = "{{{\n}}}", .output = "
" }, { // Spec: In preformatted blocks, since markers must not be preceded by leading spaces, lines with three closing braces // which belong to the preformatted block must follow at least one space. In the rendered output, one leading space is removed. .name = "", .input = "{{{\nif (x != NULL) {\n for (i = 0; i < size; i++) {\n if (x[i] > 0) {\n x[i]--;\n }}}\n}}}\n", .output = "
if (x != NULL) {\n  for (i = 0; i < size; i++) {\n    if (x[i] > 0) {\n      x[i]--;\n  }}}
", }, #if 0 { .name = "Simple unordered list", .input = "* list item\n*list item 2", .output = "
  • list item
  • \n
  • list item 2
" }, { .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 = "
  • Item
      \n
    • Subitem
" }, { .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 = "
  • Item
      \n
    1. Subitem
" }, { .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!

" }, #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"); } } }