diff --git a/api_test/main.c b/api_test/main.c index 68412f335..66ee0110f 100644 --- a/api_test/main.c +++ b/api_test/main.c @@ -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", @@ -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, "
Object.new
Object.new
");
} 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, "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, "\">");
+ render_code_info_attrs(html, &node->as.code.info, options, true);
+ cmark_strbuf_puts(html, ">");
} else {
cmark_strbuf_puts(html, "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, ">as.code.info, options, false);
+ cmark_strbuf_putc(html, '>');
}
}
@@ -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, "");
- escape_html(html, node->as.literal.data, node->as.literal.len);
+ cmark_strbuf_puts(html, "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, "");
break;
diff --git a/src/inlines.c b/src/inlines.c
index 30f2c1700..453c89b2c 100644
--- a/src/inlines.c
+++ b/src/inlines.c
@@ -24,7 +24,6 @@ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";
// Macros for creating various kinds of simple.
#define make_str(subj, sc, ec, s) make_literal(subj, CMARK_NODE_TEXT, sc, ec, s)
-#define make_code(subj, sc, ec, s) make_literal(subj, CMARK_NODE_CODE, sc, ec, s)
#define make_raw_html(subj, sc, ec, s) make_literal(subj, CMARK_NODE_HTML_INLINE, sc, ec, s)
#define make_linebreak(mem) make_simple(mem, CMARK_NODE_LINEBREAK)
#define make_softbreak(mem) make_simple(mem, CMARK_NODE_SOFTBREAK)
@@ -104,6 +103,18 @@ static CMARK_INLINE cmark_node *make_simple(cmark_mem *mem, cmark_node_type t) {
return e;
}
+static CMARK_INLINE cmark_node *make_code(subject *subj, int start_column,
+ int end_column, cmark_chunk literal) {
+ cmark_node *e = (cmark_node *)subj->mem->calloc(1, sizeof(*e));
+ cmark_strbuf_init(subj->mem, &e->content, 0);
+ e->type = CMARK_NODE_CODE;
+ e->as.code.literal = literal;
+ e->start_line = e->end_line = subj->line;
+ e->start_column = start_column + 1 + subj->column_offset + subj->block_offset;
+ e->end_column = end_column + 1 + subj->column_offset + subj->block_offset;
+ return e;
+}
+
// Like make_str, but parses entities.
static cmark_node *make_str_with_entities(subject *subj,
int start_column, int end_column,
@@ -385,6 +396,39 @@ static void S_normalize_code(cmark_strbuf *s) {
}
+static bufsize_t scan_inline_code_info_prefix(subject *subj, bufsize_t start) {
+ if (start >= subj->input.len ||
+ !cmark_is_inline_code_info_start_char((char)subj->input.data[start]) ||
+ (start > 0 && cmark_is_inline_code_info_char(
+ (char)subj->input.data[start - 1]))) {
+ return 0;
+ }
+
+ bufsize_t pos = start + 1;
+ while (pos < subj->input.len && cmark_is_inline_code_info_char(
+ (char)subj->input.data[pos])) {
+ pos++;
+ }
+
+ if (pos + 1 < subj->input.len && subj->input.data[pos] == ':' &&
+ subj->input.data[pos + 1] == '`') {
+ return pos;
+ }
+
+ return 0;
+}
+
+static bufsize_t find_inline_code_info_prefix(subject *subj, bufsize_t start,
+ bufsize_t limit) {
+ while (start < limit) {
+ if (scan_inline_code_info_prefix(subj, start)) {
+ return start;
+ }
+ start++;
+ }
+
+ return 0;
+}
// Parse backtick code section or raw backticks, return an inline.
// Assumes that the subject has a backtick at the current position.
@@ -403,12 +447,35 @@ static cmark_node *handle_backticks(subject *subj, int options) {
endpos - startpos - openticks.len);
S_normalize_code(&buf);
- cmark_node *node = make_code(subj, startpos, endpos - openticks.len - 1, cmark_chunk_buf_detach(&buf));
- adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len, options);
+ cmark_node *node = make_code(subj, startpos, endpos - openticks.len - 1,
+ cmark_chunk_buf_detach(&buf));
+ adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len,
+ options);
return node;
}
}
+static cmark_node *handle_inline_code_info(subject *subj, int options) {
+ bufsize_t startpos = subj->pos;
+ bufsize_t colonpos = scan_inline_code_info_prefix(subj, startpos);
+ if (colonpos == 0) {
+ return NULL;
+ }
+
+ cmark_chunk info =
+ cmark_chunk_dup(&subj->input, startpos, colonpos - startpos);
+ subj->pos = colonpos + 1;
+
+ cmark_node *node = handle_backticks(subj, options);
+ if (node->type != CMARK_NODE_CODE) {
+ cmark_node_free(node);
+ subj->pos = startpos;
+ return NULL;
+ }
+
+ node->as.code.info = chunk_clone(subj->mem, &info);
+ return node;
+}
// Scan ***, **, or * and return number scanned, or 0.
// Advances position.
@@ -1458,6 +1525,14 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent,
if (c == 0) {
return 0;
}
+ if (options & CMARK_OPT_INLINE_CODE_INFO) {
+ new_inl = handle_inline_code_info(subj, options);
+ }
+ if (new_inl != NULL) {
+ append_child(parent, new_inl);
+ return 1;
+ }
+
switch (c) {
case '\r':
case '\n':
@@ -1511,6 +1586,13 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent,
break;
endpos = subject_find_special_char(subj, options);
+ if (options & CMARK_OPT_INLINE_CODE_INFO) {
+ bufsize_t info_start =
+ find_inline_code_info_prefix(subj, subj->pos + 1, endpos);
+ if (info_start > 0 && info_start < endpos) {
+ endpos = info_start;
+ }
+ }
contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos);
startpos = subj->pos;
subj->pos = endpos;
diff --git a/src/iterator.c b/src/iterator.c
index 13fdb7616..edd24901b 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -139,10 +139,15 @@ void cmark_node_own(cmark_node *root) {
switch (cur->type) {
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
- case CMARK_NODE_CODE:
case CMARK_NODE_HTML_BLOCK:
cmark_chunk_to_cstr(iter->mem, &cur->as.literal);
break;
+ case CMARK_NODE_CODE:
+ cmark_chunk_to_cstr(iter->mem, &cur->as.code.literal);
+ if (cur->as.code.info.len > 0) {
+ cmark_chunk_to_cstr(iter->mem, &cur->as.code.info);
+ }
+ break;
case CMARK_NODE_LINK:
cmark_chunk_to_cstr(iter->mem, &cur->as.link.url);
cmark_chunk_to_cstr(iter->mem, &cur->as.link.title);
diff --git a/src/main.c b/src/main.c
index a62c4f2ca..25885fdd9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -64,6 +64,7 @@ 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(" --inline-code-info Parse inline code language prefixes\n");
printf(" --help, -h Print usage information\n");
printf(" --version Print version\n");
}
@@ -165,6 +166,8 @@ int main(int argc, char *argv[]) {
goto success;
} else if (strcmp(argv[i], "--full-info-string") == 0) {
options |= CMARK_OPT_FULL_INFO_STRING;
+ } else if (strcmp(argv[i], "--inline-code-info") == 0) {
+ options |= CMARK_OPT_INLINE_CODE_INFO;
} else if (strcmp(argv[i], "--table-prefer-style-attributes") == 0) {
options |= CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES;
} else if (strcmp(argv[i], "--strikethrough-double-tilde") == 0) {
diff --git a/src/node.c b/src/node.c
index e8c503409..4147aa594 100644
--- a/src/node.c
+++ b/src/node.c
@@ -149,12 +149,12 @@ static void free_node_as(cmark_node *node) {
switch (node->type) {
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_FRONT_MATTER:
+ case CMARK_NODE_CODE:
cmark_chunk_free(NODE_MEM(node), &node->as.code.info);
cmark_chunk_free(NODE_MEM(node), &node->as.code.literal);
break;
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
- case CMARK_NODE_CODE:
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_FOOTNOTE_REFERENCE:
case CMARK_NODE_FOOTNOTE_DEFINITION:
@@ -378,11 +378,11 @@ const char *cmark_node_get_literal(cmark_node *node) {
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
- case CMARK_NODE_CODE:
case CMARK_NODE_FOOTNOTE_REFERENCE:
case CMARK_NODE_FOOTNOTE_DEFINITION:
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);
+ case CMARK_NODE_CODE:
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_FRONT_MATTER:
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.literal);
@@ -403,11 +403,11 @@ int cmark_node_set_literal(cmark_node *node, const char *content) {
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
- case CMARK_NODE_CODE:
case CMARK_NODE_FOOTNOTE_REFERENCE:
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.literal, content);
return 1;
+ case CMARK_NODE_CODE:
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_FRONT_MATTER:
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
@@ -595,6 +595,59 @@ int cmark_node_set_item_index(cmark_node *node, int idx) {
}
}
+const char *cmark_node_get_code_info(cmark_node *node) {
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (node->type == CMARK_NODE_CODE ||
+ node->type == CMARK_NODE_CODE_BLOCK ||
+ node->type == CMARK_NODE_FRONT_MATTER) {
+ return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.info);
+ } else {
+ return NULL;
+ }
+}
+
+static int valid_inline_code_info(const char *info) {
+ if (info == NULL || *info == '\0') {
+ return 1;
+ }
+
+ if (!cmark_is_inline_code_info_start_char(*info)) {
+ return 0;
+ }
+
+ while (*++info) {
+ if (!cmark_is_inline_code_info_char(*info)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int cmark_node_set_code_info(cmark_node *node, const char *info) {
+ if (node == NULL) {
+ return 0;
+ }
+
+ if (node->type == CMARK_NODE_CODE) {
+ if (!valid_inline_code_info(info)) {
+ return 0;
+ }
+
+ cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
+ return 1;
+ } else if (node->type == CMARK_NODE_CODE_BLOCK ||
+ node->type == CMARK_NODE_FRONT_MATTER) {
+ cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
const char *cmark_node_get_fence_info(cmark_node *node) {
if (node == NULL) {
return NULL;
diff --git a/src/xml.c b/src/xml.c
index 6358e3dfb..6fa979dcf 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -72,7 +72,6 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
literal = true;
break;
case CMARK_NODE_TEXT:
- case CMARK_NODE_CODE:
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_HTML_INLINE:
cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
@@ -81,6 +80,18 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
literal = true;
break;
+ case CMARK_NODE_CODE:
+ if (node->as.code.info.len > 0) {
+ cmark_strbuf_puts(xml, " info=\"");
+ escape_xml(xml, node->as.code.info.data, node->as.code.info.len);
+ cmark_strbuf_putc(xml, '"');
+ }
+ cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
+ escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len);
+ cmark_strbuf_puts(xml, "");
+ cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
+ literal = true;
+ break;
case CMARK_NODE_LIST:
switch (cmark_node_get_list_type(node)) {
case CMARK_ORDERED_LIST:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 2b5c99bc5..1d53a8270 100755
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -96,6 +96,20 @@ IF (PYTHONINTERP_FOUND)
"--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm --full-info-string"
)
+ add_test(option_inline_code_info
+ ${PYTHON_EXECUTABLE}
+ "${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py"
+ "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/inline-code-info.txt"
+ "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm --inline-code-info"
+ )
+
+ add_test(option_inline_code_info_smart
+ ${PYTHON_EXECUTABLE}
+ "${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py"
+ "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/inline-code-info-smart.txt"
+ "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark-gfm --inline-code-info --smart"
+ )
+
add_test(regressiontest_executable
${PYTHON_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/spec_tests.py" "--no-normalize" "--spec"
@@ -111,4 +125,3 @@ ELSE(PYTHONINTERP_FOUND)
echo "Skipping spec tests, because no python 3 interpreter is available.")
ENDIF(PYTHONINTERP_FOUND)
-
diff --git a/test/inline-code-info-smart.txt b/test/inline-code-info-smart.txt
new file mode 100644
index 000000000..9b99d4c15
--- /dev/null
+++ b/test/inline-code-info-smart.txt
@@ -0,0 +1,14 @@
+### Inline code language prefixes with smart punctuation
+
+Language prefixes containing punctuation are recognized before smart
+punctuation processing.
+
+```````````````````````````````` example
+Objective-C: objective-c:`NSObject`
+Dotted: foo.bar:`value`
+Underscored: foo_bar:`value`
+.
+Objective-C: NSObject
+Dotted: value
+Underscored: value
+````````````````````````````````
diff --git a/test/inline-code-info.txt b/test/inline-code-info.txt
new file mode 100644
index 000000000..df36b9674
--- /dev/null
+++ b/test/inline-code-info.txt
@@ -0,0 +1,53 @@
+### Inline code language prefixes
+
+Inline code spans may carry language prefixes.
+
+```````````````````````````````` example
+Ruby code: ruby:`Object.new`
+.
+Ruby code: Object.new
+````````````````````````````````
+
+Language prefixes work with longer code span delimiters.
+
+```````````````````````````````` example
+Ruby code: ruby:`` `Object.new` ``
+.
+Ruby code: `Object.new`
+````````````````````````````````
+
+Language identifiers may contain characters used by common language names.
+
+```````````````````````````````` example
+C++ code: c++:`std::vector`
+C# code: c#:`System.Object`
+Objective-C code: objective-c:`NSObject`
+Ruby template: ruby_template:`render`
+.
+C++ code: std::vector
+C# code: System.Object
+Objective-C code: NSObject
+Ruby template: render
+````````````````````````````````
+
+Ordinary punctuation following inline code is preserved.
+
+```````````````````````````````` example
+Use `x`: then continue.
+.
+Use x: then continue.
+````````````````````````````````
+
+Invalid and empty language prefixes remain literal text.
+
+```````````````````````````````` example
+Empty: :`x`
+Quoted: "ruby":`x`
+Invalid: ruby/:`x`
+Unclosed: ruby:`x
+.
+Empty: :x
+Quoted: "ruby":x
+Invalid: ruby/:x
+Unclosed: ruby:`x
+````````````````````````````````
diff --git a/test/regression.txt b/test/regression.txt
index 0ec493d3d..4fb990334 100644
--- a/test/regression.txt
+++ b/test/regression.txt
@@ -11,6 +11,14 @@ line2
line2
````````````````````````````````
+Inline code language prefixes are not parsed unless explicitly enabled.
+
+```````````````````````````````` example
+Ruby code: ruby:`Object.new`
+.
+Ruby code: ruby:Object.new
+````````````````````````````````
+
Issue #114: cmark skipping first character in line
(Important: the blank lines around "Repeatedly" contain a tab.)