Skip to content

Commit

Permalink
optionally allow Reddit-style spoiler delimiters
Browse files Browse the repository at this point in the history
  • Loading branch information
QuietMisdreavus committed Dec 29, 2021
1 parent de73260 commit dce9a9b
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 27 deletions.
68 changes: 68 additions & 0 deletions api_test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,74 @@ static void render_spoiler(test_batch_runner *runner) {
" </paragraph>\n"
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
}
{
static const char markdown[] = "we have some >!incorrectly spicy text!< here";
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);

cmark_node *doc = cmark_parser_finish(parser);
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
" <paragraph>\n"
" <text xml:space=\"preserve\">we have some &gt;!incorrectly spicy text!&lt; here</text>\n"
" </paragraph>\n"
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
}
}

static void render_spoiler_reddit(test_batch_runner *runner) {
cmark_gfm_core_extensions_ensure_registered();

cmark_parser *parser = cmark_parser_new(CMARK_OPT_SPOILER_REDDIT_STYLE);
cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("spoiler"));

{
static const char markdown[] = "we have some >!spicy text!< here";
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);

cmark_node *doc = cmark_parser_finish(parser);
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
" <paragraph>\n"
" <text xml:space=\"preserve\">we have some </text>\n"
" <spoiler>\n"
" <text xml:space=\"preserve\">spicy text</text>\n"
" </spoiler>\n"
" <text xml:space=\"preserve\"> here</text>\n"
" </paragraph>\n"
"</document>\n", "rendering spoilers should appear correctly");
}
{
static const char markdown[] = "we have some !non-spicy text! here";
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);

cmark_node *doc = cmark_parser_finish(parser);
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
" <paragraph>\n"
" <text xml:space=\"preserve\">we have some !non-spicy text! here</text>\n"
" </paragraph>\n"
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
}
{
static const char markdown[] = "we have some ||incorrectly spicy text|| here";
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);

cmark_node *doc = cmark_parser_finish(parser);
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
" <paragraph>\n"
" <text xml:space=\"preserve\">we have some ||incorrectly spicy text|| here</text>\n"
" </paragraph>\n"
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
}
}

int main() {
Expand Down
5 changes: 5 additions & 0 deletions bin/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ void print_usage() {
" instead of align attributes.\n");
printf(" --full-info-string Include remainder of code block info\n"
" string in a separate attribute.\n");
printf(" --spoiler-reddit-style Use Reddit-style spoiler delimiters,\n"
" i.e. >!spoilertext!< instead of\n"
" ||spoilertext||\n");
printf(" --help, -h Print usage information\n");
printf(" --version Print version\n");
}
Expand Down Expand Up @@ -185,6 +188,8 @@ int main(int argc, char *argv[]) {
options |= CMARK_OPT_VALIDATE_UTF8;
} else if (strcmp(argv[i], "--liberal-html-tag") == 0) {
options |= CMARK_OPT_LIBERAL_HTML_TAG;
} else if (strcmp(argv[i], "--spoiler-reddit-style") == 0) {
options |= CMARK_OPT_SPOILER_REDDIT_STYLE;
} else if ((strcmp(argv[i], "--help") == 0) ||
(strcmp(argv[i], "-h") == 0)) {
print_usage();
Expand Down
109 changes: 82 additions & 27 deletions extensions/spoiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,74 @@ static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
int left_flanking, right_flanking, punct_before, punct_after, delims;
char buffer[101];

if (character != '|')
return NULL;

delims = cmark_inline_parser_scan_delimiters(
inline_parser, sizeof(buffer) - 1, '|',
&left_flanking,
&right_flanking, &punct_before, &punct_after);

memset(buffer, '|', delims);
buffer[delims] = 0;

res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, buffer);
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;

if ((left_flanking || right_flanking) && (delims == 2)) {
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
right_flanking, res);
if ((parser->options & CMARK_OPT_SPOILER_REDDIT_STYLE)) {
// Reddit-style spoilers - flanked by angle brackets and exclamation marks,
// e.g. >!this is a spoiler!<
int pos = cmark_inline_parser_get_offset(inline_parser);
char *txt = NULL;
bool opener = false;
bool closer = false;
if (cmark_inline_parser_peek_at(inline_parser, pos) == '>' &&
cmark_inline_parser_peek_at(inline_parser, pos + 1) == '!') {
txt = ">!";
opener = true;
} else if (cmark_inline_parser_peek_at(inline_parser, pos) == '!' &&
cmark_inline_parser_peek_at(inline_parser, pos + 1) == '<') {
txt = "!<";
closer = true;
}

if (opener && pos > 0 && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos - 1))) {
opener = false;
}

if (closer) {
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
bufsize_t len = chunk->len;
if (pos + 2 < len && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos + 2))) {
closer = false;
}
}

if ((!opener && !closer) || !txt)
return NULL;

res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, txt);
res->start_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser);

cmark_inline_parser_set_offset(inline_parser, pos + 2);

res->end_line = cmark_inline_parser_get_line(inline_parser);
res->end_column = cmark_inline_parser_get_column(inline_parser);

// Set the character for this delimiter to `!`, since it's a heterogenous
// delimiter and the delimiter API assumes single repeated characters.
cmark_inline_parser_push_delimiter(inline_parser, '!', opener, closer, res);
} else {
// Discord-style spoilers - flanked on both sides by two pipes,
// e.g. ||this is a spoiler||
if (character != '|')
return NULL;

delims = cmark_inline_parser_scan_delimiters(
inline_parser, sizeof(buffer) - 1, '|',
&left_flanking,
&right_flanking, &punct_before, &punct_after);

memset(buffer, '|', delims);
buffer[delims] = 0;

res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, buffer);
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;

if ((left_flanking || right_flanking) && (delims == 2)) {
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
right_flanking, res);
}
}

return res;
Expand Down Expand Up @@ -95,7 +144,16 @@ static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
static void commonmark_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
renderer->out(renderer, node, "||", false, LITERAL);
if (options & CMARK_OPT_SPOILER_REDDIT_STYLE) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->out(renderer, node, ">!", false, LITERAL);
} else {
renderer->out(renderer, node, "!<", false, LITERAL);
}
} else {
renderer->out(renderer, node, "||", false, LITERAL);
}
}

static void html_render(cmark_syntax_extension *extension,
Expand All @@ -109,12 +167,6 @@ static void html_render(cmark_syntax_extension *extension,
}
}

static void plaintext_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
renderer->out(renderer, node, "~", false, LITERAL);
}

cmark_syntax_extension *create_spoiler_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("spoiler");
cmark_llist *special_chars = NULL;
Expand All @@ -123,14 +175,17 @@ cmark_syntax_extension *create_spoiler_extension(void) {
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
cmark_syntax_extension_set_html_render_func(ext, html_render);
cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render);
cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
CMARK_NODE_SPOILER = cmark_syntax_extension_add_node(1);

cmark_syntax_extension_set_match_inline_func(ext, match);
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);

cmark_mem *mem = cmark_get_default_mem_allocator();
special_chars = cmark_llist_append(mem, special_chars, (void *)'|');
special_chars = cmark_llist_append(mem, special_chars, (void *)'>');
special_chars = cmark_llist_append(mem, special_chars, (void *)'<');
special_chars = cmark_llist_append(mem, special_chars, (void *)'!');
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);

cmark_syntax_extension_set_emphasis(ext, 1);
Expand Down
5 changes: 5 additions & 0 deletions src/include/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,11 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
*/
#define CMARK_OPT_PRESERVE_WHITESPACE ((1 << 19) | CMARK_OPT_INLINE_ONLY)

/** Parse spoiler text with Reddit-style delimiters (`>!this is a spoiler!<`). Without
* this option, spoilers are parsed with Discord-style delimiters (`||this is a spoiler||`).
*/
#define CMARK_OPT_SPOILER_REDDIT_STYLE (1 << 20)

/**
* ## Version information
*/
Expand Down
9 changes: 9 additions & 0 deletions test/spec.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7752,6 +7752,15 @@ Spoiler alert: ||Hello, world!||
<p>Spoiler alert: <span class="spoiler">Hello, world!</span></p>
````````````````````````````````

The `spoiler` extension also optionally allows alternate "Reddit-style" delimiters,
which use angle brackets and exclamation marks to mark spoiler text:

```````````````````````````````` example disabled
Spoiler alert: >!Hello, world!!<
.
<p>Spoiler alert: <span class="spoiler">Hello, world!</span></p>
````````````````````````````````

</div>

## Links
Expand Down

0 comments on commit dce9a9b

Please sign in to comment.