Skip to content

Commit dce9a9b

Browse files
optionally allow Reddit-style spoiler delimiters
1 parent de73260 commit dce9a9b

File tree

5 files changed

+169
-27
lines changed

5 files changed

+169
-27
lines changed

api_test/main.c

+68
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,74 @@ static void render_spoiler(test_batch_runner *runner) {
14551455
" </paragraph>\n"
14561456
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
14571457
}
1458+
{
1459+
static const char markdown[] = "we have some >!incorrectly spicy text!< here";
1460+
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
1461+
1462+
cmark_node *doc = cmark_parser_finish(parser);
1463+
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
1464+
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1465+
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
1466+
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
1467+
" <paragraph>\n"
1468+
" <text xml:space=\"preserve\">we have some &gt;!incorrectly spicy text!&lt; here</text>\n"
1469+
" </paragraph>\n"
1470+
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
1471+
}
1472+
}
1473+
1474+
static void render_spoiler_reddit(test_batch_runner *runner) {
1475+
cmark_gfm_core_extensions_ensure_registered();
1476+
1477+
cmark_parser *parser = cmark_parser_new(CMARK_OPT_SPOILER_REDDIT_STYLE);
1478+
cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("spoiler"));
1479+
1480+
{
1481+
static const char markdown[] = "we have some >!spicy text!< here";
1482+
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
1483+
1484+
cmark_node *doc = cmark_parser_finish(parser);
1485+
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
1486+
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1487+
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
1488+
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
1489+
" <paragraph>\n"
1490+
" <text xml:space=\"preserve\">we have some </text>\n"
1491+
" <spoiler>\n"
1492+
" <text xml:space=\"preserve\">spicy text</text>\n"
1493+
" </spoiler>\n"
1494+
" <text xml:space=\"preserve\"> here</text>\n"
1495+
" </paragraph>\n"
1496+
"</document>\n", "rendering spoilers should appear correctly");
1497+
}
1498+
{
1499+
static const char markdown[] = "we have some !non-spicy text! here";
1500+
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
1501+
1502+
cmark_node *doc = cmark_parser_finish(parser);
1503+
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
1504+
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1505+
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
1506+
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
1507+
" <paragraph>\n"
1508+
" <text xml:space=\"preserve\">we have some !non-spicy text! here</text>\n"
1509+
" </paragraph>\n"
1510+
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
1511+
}
1512+
{
1513+
static const char markdown[] = "we have some ||incorrectly spicy text|| here";
1514+
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
1515+
1516+
cmark_node *doc = cmark_parser_finish(parser);
1517+
char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT);
1518+
STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1519+
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"
1520+
"<document xmlns=\"http://commonmark.org/xml/1.0\">\n"
1521+
" <paragraph>\n"
1522+
" <text xml:space=\"preserve\">we have some ||incorrectly spicy text|| here</text>\n"
1523+
" </paragraph>\n"
1524+
"</document>\n", "rendering spoilers without proper delimiters should appear correctly");
1525+
}
14581526
}
14591527

14601528
int main() {

bin/main.c

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ void print_usage() {
6464
" instead of align attributes.\n");
6565
printf(" --full-info-string Include remainder of code block info\n"
6666
" string in a separate attribute.\n");
67+
printf(" --spoiler-reddit-style Use Reddit-style spoiler delimiters,\n"
68+
" i.e. >!spoilertext!< instead of\n"
69+
" ||spoilertext||\n");
6770
printf(" --help, -h Print usage information\n");
6871
printf(" --version Print version\n");
6972
}
@@ -185,6 +188,8 @@ int main(int argc, char *argv[]) {
185188
options |= CMARK_OPT_VALIDATE_UTF8;
186189
} else if (strcmp(argv[i], "--liberal-html-tag") == 0) {
187190
options |= CMARK_OPT_LIBERAL_HTML_TAG;
191+
} else if (strcmp(argv[i], "--spoiler-reddit-style") == 0) {
192+
options |= CMARK_OPT_SPOILER_REDDIT_STYLE;
188193
} else if ((strcmp(argv[i], "--help") == 0) ||
189194
(strcmp(argv[i], "-h") == 0)) {
190195
print_usage();

extensions/spoiler.c

+82-27
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,74 @@ static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
1111
int left_flanking, right_flanking, punct_before, punct_after, delims;
1212
char buffer[101];
1313

14-
if (character != '|')
15-
return NULL;
16-
17-
delims = cmark_inline_parser_scan_delimiters(
18-
inline_parser, sizeof(buffer) - 1, '|',
19-
&left_flanking,
20-
&right_flanking, &punct_before, &punct_after);
21-
22-
memset(buffer, '|', delims);
23-
buffer[delims] = 0;
24-
25-
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
26-
cmark_node_set_literal(res, buffer);
27-
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
28-
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
29-
30-
if ((left_flanking || right_flanking) && (delims == 2)) {
31-
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
32-
right_flanking, res);
14+
if ((parser->options & CMARK_OPT_SPOILER_REDDIT_STYLE)) {
15+
// Reddit-style spoilers - flanked by angle brackets and exclamation marks,
16+
// e.g. >!this is a spoiler!<
17+
int pos = cmark_inline_parser_get_offset(inline_parser);
18+
char *txt = NULL;
19+
bool opener = false;
20+
bool closer = false;
21+
if (cmark_inline_parser_peek_at(inline_parser, pos) == '>' &&
22+
cmark_inline_parser_peek_at(inline_parser, pos + 1) == '!') {
23+
txt = ">!";
24+
opener = true;
25+
} else if (cmark_inline_parser_peek_at(inline_parser, pos) == '!' &&
26+
cmark_inline_parser_peek_at(inline_parser, pos + 1) == '<') {
27+
txt = "!<";
28+
closer = true;
29+
}
30+
31+
if (opener && pos > 0 && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos - 1))) {
32+
opener = false;
33+
}
34+
35+
if (closer) {
36+
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
37+
bufsize_t len = chunk->len;
38+
if (pos + 2 < len && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos + 2))) {
39+
closer = false;
40+
}
41+
}
42+
43+
if ((!opener && !closer) || !txt)
44+
return NULL;
45+
46+
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
47+
cmark_node_set_literal(res, txt);
48+
res->start_line = cmark_inline_parser_get_line(inline_parser);
49+
res->start_column = cmark_inline_parser_get_column(inline_parser);
50+
51+
cmark_inline_parser_set_offset(inline_parser, pos + 2);
52+
53+
res->end_line = cmark_inline_parser_get_line(inline_parser);
54+
res->end_column = cmark_inline_parser_get_column(inline_parser);
55+
56+
// Set the character for this delimiter to `!`, since it's a heterogenous
57+
// delimiter and the delimiter API assumes single repeated characters.
58+
cmark_inline_parser_push_delimiter(inline_parser, '!', opener, closer, res);
59+
} else {
60+
// Discord-style spoilers - flanked on both sides by two pipes,
61+
// e.g. ||this is a spoiler||
62+
if (character != '|')
63+
return NULL;
64+
65+
delims = cmark_inline_parser_scan_delimiters(
66+
inline_parser, sizeof(buffer) - 1, '|',
67+
&left_flanking,
68+
&right_flanking, &punct_before, &punct_after);
69+
70+
memset(buffer, '|', delims);
71+
buffer[delims] = 0;
72+
73+
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
74+
cmark_node_set_literal(res, buffer);
75+
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
76+
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
77+
78+
if ((left_flanking || right_flanking) && (delims == 2)) {
79+
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
80+
right_flanking, res);
81+
}
3382
}
3483

3584
return res;
@@ -95,7 +144,16 @@ static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
95144
static void commonmark_render(cmark_syntax_extension *extension,
96145
cmark_renderer *renderer, cmark_node *node,
97146
cmark_event_type ev_type, int options) {
98-
renderer->out(renderer, node, "||", false, LITERAL);
147+
if (options & CMARK_OPT_SPOILER_REDDIT_STYLE) {
148+
bool entering = (ev_type == CMARK_EVENT_ENTER);
149+
if (entering) {
150+
renderer->out(renderer, node, ">!", false, LITERAL);
151+
} else {
152+
renderer->out(renderer, node, "!<", false, LITERAL);
153+
}
154+
} else {
155+
renderer->out(renderer, node, "||", false, LITERAL);
156+
}
99157
}
100158

101159
static void html_render(cmark_syntax_extension *extension,
@@ -109,12 +167,6 @@ static void html_render(cmark_syntax_extension *extension,
109167
}
110168
}
111169

112-
static void plaintext_render(cmark_syntax_extension *extension,
113-
cmark_renderer *renderer, cmark_node *node,
114-
cmark_event_type ev_type, int options) {
115-
renderer->out(renderer, node, "~", false, LITERAL);
116-
}
117-
118170
cmark_syntax_extension *create_spoiler_extension(void) {
119171
cmark_syntax_extension *ext = cmark_syntax_extension_new("spoiler");
120172
cmark_llist *special_chars = NULL;
@@ -123,14 +175,17 @@ cmark_syntax_extension *create_spoiler_extension(void) {
123175
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
124176
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
125177
cmark_syntax_extension_set_html_render_func(ext, html_render);
126-
cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render);
178+
cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
127179
CMARK_NODE_SPOILER = cmark_syntax_extension_add_node(1);
128180

129181
cmark_syntax_extension_set_match_inline_func(ext, match);
130182
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);
131183

132184
cmark_mem *mem = cmark_get_default_mem_allocator();
133185
special_chars = cmark_llist_append(mem, special_chars, (void *)'|');
186+
special_chars = cmark_llist_append(mem, special_chars, (void *)'>');
187+
special_chars = cmark_llist_append(mem, special_chars, (void *)'<');
188+
special_chars = cmark_llist_append(mem, special_chars, (void *)'!');
134189
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
135190

136191
cmark_syntax_extension_set_emphasis(ext, 1);

src/include/cmark-gfm.h

+5
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,11 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
780780
*/
781781
#define CMARK_OPT_PRESERVE_WHITESPACE ((1 << 19) | CMARK_OPT_INLINE_ONLY)
782782

783+
/** Parse spoiler text with Reddit-style delimiters (`>!this is a spoiler!<`). Without
784+
* this option, spoilers are parsed with Discord-style delimiters (`||this is a spoiler||`).
785+
*/
786+
#define CMARK_OPT_SPOILER_REDDIT_STYLE (1 << 20)
787+
783788
/**
784789
* ## Version information
785790
*/

test/spec.txt

+9
Original file line numberDiff line numberDiff line change
@@ -7752,6 +7752,15 @@ Spoiler alert: ||Hello, world!||
77527752
<p>Spoiler alert: <span class="spoiler">Hello, world!</span></p>
77537753
````````````````````````````````
77547754

7755+
The `spoiler` extension also optionally allows alternate "Reddit-style" delimiters,
7756+
which use angle brackets and exclamation marks to mark spoiler text:
7757+
7758+
```````````````````````````````` example disabled
7759+
Spoiler alert: >!Hello, world!!<
7760+
.
7761+
<p>Spoiler alert: <span class="spoiler">Hello, world!</span></p>
7762+
````````````````````````````````
7763+
77557764
</div>
77567765

77577766
## Links

0 commit comments

Comments
 (0)