Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions api_test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ static void accessors(test_batch_runner *runner) {
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");
STR_EQ(runner, cmark_node_get_code_info(fenced), "lang", "get_code_info");

cmark_node *code = cmark_node_next(fenced);
STR_EQ(runner, cmark_node_get_literal(code), "code\n",
Expand Down Expand Up @@ -163,6 +164,8 @@ static void accessors(test_batch_runner *runner) {
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");
STR_EQ(runner, cmark_node_get_code_info(fenced), "LANG",
"get updated code info");

OK(runner, cmark_node_set_literal(html, "<div>HTML</div>\n"),
"set_literal html");
Expand Down Expand Up @@ -243,6 +246,66 @@ static void accessors(test_batch_runner *runner) {
cmark_node_free(doc);
}

static void inline_code_info(test_batch_runner *runner) {
static const char markdown[] = "ruby:`Object.new`";
cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1,
CMARK_OPT_INLINE_CODE_INFO);
cmark_node *paragraph = cmark_node_first_child(doc);
cmark_node *code = cmark_node_first_child(paragraph);

INT_EQ(runner, cmark_node_get_type(code), CMARK_NODE_CODE,
"inline code language node type");
STR_EQ(runner, cmark_node_get_literal(code), "Object.new",
"inline code language literal");
STR_EQ(runner, cmark_node_get_code_info(code), "ruby",
"get inline code info");

OK(runner, cmark_node_get_fence_info(code) == NULL,
"fence info rejects inline code");
OK(runner, !cmark_node_set_fence_info(code, "c++"),
"set fence info rejects inline code");
OK(runner, cmark_node_get_code_info(paragraph) == NULL,
"code info rejects paragraph");
OK(runner, !cmark_node_set_code_info(paragraph, "ruby"),
"set code info rejects paragraph");

char *html = cmark_render_html(doc, CMARK_OPT_DEFAULT, NULL);
STR_EQ(runner, html,
"<p><code class=\"language-ruby\">Object.new</code></p>\n",
"render inline code language");
free(html);

char *commonmark = cmark_render_commonmark(doc, CMARK_OPT_DEFAULT, 0);
STR_EQ(runner, commonmark, "ruby:`Object.new`\n",
"render inline code language as commonmark");
free(commonmark);

OK(runner, !cmark_node_set_code_info(code, "ruby lineno=5"),
"reject invalid inline code info");
STR_EQ(runner, cmark_node_get_code_info(code), "ruby",
"preserve inline code info after invalid set");

OK(runner, cmark_node_set_code_info(code, "c++"), "set inline code info");
STR_EQ(runner, cmark_node_get_code_info(code), "c++",
"get updated inline code info");

OK(runner, cmark_node_set_code_info(code, "a_b-c+#.d"),
"set inline code info with all supported punctuation");
STR_EQ(runner, cmark_node_get_code_info(code), "a_b-c+#.d",
"get inline code info with all supported punctuation");

OK(runner, cmark_node_set_code_info(code, ""), "clear inline code info");
html = cmark_render_html(doc, CMARK_OPT_DEFAULT, NULL);
STR_EQ(runner, html, "<p><code>Object.new</code></p>\n",
"render inline code without empty language class");
free(html);

OK(runner, cmark_node_set_code_info(code, NULL),
"clear inline code info with null");

cmark_node_free(doc);
}

static void node_check(test_batch_runner *runner) {
// Construct an incomplete tree.
cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT);
Expand Down Expand Up @@ -1200,6 +1263,7 @@ int main() {
version(runner);
constructor(runner);
accessors(runner);
inline_code_info(runner);
node_check(runner);
iterator(runner);
iterator_delete(runner);
Expand Down
4 changes: 4 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[unreleased]

* Add `CMARK_OPT_INLINE_CODE_INFO`: parse language-prefixed inline code such
as ``ruby:`Object.new``` and render the language as a `language-ruby`
class.

* Add `CMARK_OPT_FRONT_MATTER`: parse a "---" delimited block at the start
of a document and expose it as a `CMARK_NODE_FRONT_MATTER` node. An
optional info string (e.g. "--- yaml") records the content format;
Expand Down
4 changes: 4 additions & 0 deletions man/man1/cmark-gfm.1
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ be rendered as curly quotes, depending on their position.
\f[C]\-\-\-\f[] will be rendered as an em-dash.
\f[C]...\f[] will be rendered as ellipses.
.TP 12n
.B \-\-inline-code-info
Parse language prefixes on inline code spans, such as
\f[C]ruby:`Object.new`\f[], and expose the language identifier as code info.
.TP 12n
.B \-\-unsafe
Render raw HTML and potentially dangerous URLs.
(Raw HTML is not replaced by a placeholder comment; potentially
Expand Down
30 changes: 29 additions & 1 deletion man/man3/cmark-gfm.3
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,23 @@ Returns 1 if \f[I]node\f[] is a tight list, 0 otherwise.
.PP
Sets the "tightness" of a list. Returns 1 on success, 0 on failure.

.PP
\fIconst char *\f[] \fBcmark_node_get_code_info\f[](\fIcmark_node *node\f[])

.PP
Returns the info string from a code block or the language identifier from
an inline code span.

.PP
\fIint\f[] \fBcmark_node_set_code_info\f[](\fIcmark_node *node\f[], \fIconst char *info\f[])

.PP
Sets the info string in a code block or the language identifier on an
inline code span, returning 1 on success and 0 on failure. Inline code
language identifiers must begin with an alphanumeric character and may
additionally contain \f[C]_\f[], \f[C]-\f[], \f[C]+\f[], \f[C]#\f[],
and \f[C].\f[].

.PP
\fIconst char *\f[] \fBcmark_node_get_fence_info\f[](\fIcmark_node *node\f[])

Expand Down Expand Up @@ -1009,6 +1026,18 @@ Use style attributes to align table cells instead of align attributes.
Include the remainder of the info string in code blocks in a separate
attribute.

.PP
.nf
\fC
.RS 0n
#define CMARK_OPT_INLINE_CODE_INFO (1 << 19)
.RE
\f[]
.fi

.PP
Parse inline code language prefixes, e.g. \f[C]ruby:`code`\f[].

.SS
Version information

Expand Down Expand Up @@ -1038,4 +1067,3 @@ CMARK_VERSION_STRING for compile time checks.
AUTHORS
.PP
John MacFarlane, Vicent Marti, Kārlis Gaņģis, Nick Wellnhofer.

16 changes: 16 additions & 0 deletions src/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node);
*/
CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx);

/** Returns the info string from a code block or the language identifier from
* an inline code span.
*/
CMARK_GFM_EXPORT const char *cmark_node_get_code_info(cmark_node *node);

/** Sets the info string in a code block or the language identifier on an
* inline code span, returning 1 on success and 0 on failure. Inline code
* language identifiers must begin with an alphanumeric character and may
* additionally contain '_', '-', '+', '#', and '.'.
*/
CMARK_GFM_EXPORT int cmark_node_set_code_info(cmark_node *node, const char *info);

/** Returns the info string from a fenced code block.
*/
CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node);
Expand Down Expand Up @@ -776,6 +788,10 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
*/
#define CMARK_OPT_FRONT_MATTER (1 << 18)

/** Parse inline code language prefixes, e.g. ruby:`code`.
*/
#define CMARK_OPT_INLINE_CODE_INFO (1 << 19)

/**
* ## Version information
*/
Expand Down
9 changes: 9 additions & 0 deletions src/cmark_ctype.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,12 @@ int cmark_isalnum(char c) {
int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; }

int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; }

int cmark_is_inline_code_info_start_char(char c) {
return cmark_isalnum(c);
}

int cmark_is_inline_code_info_char(char c) {
return cmark_is_inline_code_info_start_char(c) || c == '_' || c == '-' ||
c == '+' || c == '#' || c == '.';
}
5 changes: 5 additions & 0 deletions src/cmark_ctype.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ int cmark_isdigit(char c);
CMARK_GFM_EXPORT
int cmark_isalpha(char c);

/* Character classes for inline code info identifiers. */
int cmark_is_inline_code_info_start_char(char c);

int cmark_is_inline_code_info_char(char c);

#ifdef __cplusplus
}
#endif
Expand Down
6 changes: 5 additions & 1 deletion src/commonmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
if (!first_in_list_item) {
BLANKLINE();
}
info = cmark_node_get_fence_info(node);
info = cmark_node_get_code_info(node);
info_len = strlen(info);
fencechar[0] = strchr(info, '`') == NULL ? '`' : '~';
code = cmark_node_get_literal(node);
Expand Down Expand Up @@ -363,6 +363,10 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
extra_spaces = code_len == 0 ||
code[0] == '`' || code[code_len - 1] == '`' ||
code[0] == ' ' || code[code_len - 1] == ' ';
if (node->as.code.info.len > 0) {
OUT(cmark_node_get_code_info(node), false, LITERAL);
LIT(":");
}
for (i = 0; i < numticks; i++) {
LIT("`");
}
Expand Down
67 changes: 44 additions & 23 deletions src/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ static void escape_html(cmark_strbuf *dest, const unsigned char *source,
houdini_escape_html0(dest, source, length, 0);
}

static bufsize_t first_code_info_tag(cmark_chunk *info) {
bufsize_t first_tag = 0;
while (first_tag < info->len && !cmark_isspace(info->data[first_tag])) {
first_tag += 1;
}
return first_tag;
}

static void render_code_info_attrs(cmark_strbuf *html, cmark_chunk *info,
int options, bool pre_lang) {
bufsize_t first_tag = first_code_info_tag(info);

if (pre_lang) {
cmark_strbuf_puts(html, " lang=\"");
} else {
cmark_strbuf_puts(html, " class=\"language-");
}

escape_html(html, info->data, first_tag);

if (first_tag < info->len && (options & CMARK_OPT_FULL_INFO_STRING)) {
cmark_strbuf_puts(html, "\" data-meta=\"");
escape_html(html, info->data + first_tag + 1, info->len - first_tag - 1);
}

cmark_strbuf_putc(html, '"');
}

static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size_t len) {
cmark_strbuf *html = renderer->html;
cmark_llist *it;
Expand Down Expand Up @@ -125,7 +153,11 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
case CMARK_NODE_TEXT:
case CMARK_NODE_CODE:
case CMARK_NODE_HTML_INLINE:
escape_html(html, node->as.literal.data, node->as.literal.len);
if (node->type == CMARK_NODE_CODE) {
escape_html(html, node->as.code.literal.data, node->as.code.literal.len);
} else {
escape_html(html, node->as.literal.data, node->as.literal.len);
}
break;

case CMARK_NODE_LINEBREAK:
Expand Down Expand Up @@ -220,32 +252,17 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_puts(html, "><code>");
} else {
bufsize_t first_tag = 0;
while (first_tag < node->as.code.info.len &&
!cmark_isspace(node->as.code.info.data[first_tag])) {
first_tag += 1;
}

if (options & CMARK_OPT_GITHUB_PRE_LANG) {
cmark_strbuf_puts(html, "<pre");
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_puts(html, " lang=\"");
escape_html(html, node->as.code.info.data, first_tag);
if (first_tag < node->as.code.info.len && (options & CMARK_OPT_FULL_INFO_STRING)) {
cmark_strbuf_puts(html, "\" data-meta=\"");
escape_html(html, node->as.code.info.data + first_tag + 1, node->as.code.info.len - first_tag - 1);
}
cmark_strbuf_puts(html, "\"><code>");
render_code_info_attrs(html, &node->as.code.info, options, true);
cmark_strbuf_puts(html, "><code>");
} else {
cmark_strbuf_puts(html, "<pre");
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_puts(html, "><code class=\"language-");
escape_html(html, node->as.code.info.data, first_tag);
if (first_tag < node->as.code.info.len && (options & CMARK_OPT_FULL_INFO_STRING)) {
cmark_strbuf_puts(html, "\" data-meta=\"");
escape_html(html, node->as.code.info.data + first_tag + 1, node->as.code.info.len - first_tag - 1);
}
cmark_strbuf_puts(html, "\">");
cmark_strbuf_puts(html, "><code");
render_code_info_attrs(html, &node->as.code.info, options, false);
cmark_strbuf_putc(html, '>');
}
}

Expand Down Expand Up @@ -327,8 +344,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
break;

case CMARK_NODE_CODE:
cmark_strbuf_puts(html, "<code>");
escape_html(html, node->as.literal.data, node->as.literal.len);
cmark_strbuf_puts(html, "<code");
if (node->as.code.info.len > 0) {
render_code_info_attrs(html, &node->as.code.info, options, false);
}
cmark_strbuf_putc(html, '>');
escape_html(html, node->as.code.literal.data, node->as.code.literal.len);
cmark_strbuf_puts(html, "</code>");
break;

Expand Down
Loading
Loading