markly 0.15.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/getting-started.md +36 -0
  4. data/context/index.yaml +3 -3
  5. data/ext/markly/blocks.c +24 -6
  6. data/ext/markly/cmark-gfm.h +24 -0
  7. data/ext/markly/cmark-gfm_export.h +1 -0
  8. data/ext/markly/cmark-gfm_version.h +2 -2
  9. data/ext/markly/cmark.c +1 -1
  10. data/ext/markly/cmark_ctype.c +9 -0
  11. data/ext/markly/cmark_ctype.h +5 -0
  12. data/ext/markly/commonmark.c +21 -1
  13. data/ext/markly/config.h +0 -4
  14. data/ext/markly/extconf.rb +6 -1
  15. data/ext/markly/{table.c → extensions/table.c} +91 -46
  16. data/ext/markly/front_matter.c +141 -0
  17. data/ext/markly/front_matter.h +24 -0
  18. data/ext/markly/html.c +47 -23
  19. data/ext/markly/inlines.c +85 -3
  20. data/ext/markly/iterator.c +6 -1
  21. data/ext/markly/latex.c +3 -0
  22. data/ext/markly/man.c +3 -0
  23. data/ext/markly/markly.c +288 -201
  24. data/ext/markly/node.c +65 -5
  25. data/ext/markly/node.h +1 -0
  26. data/ext/markly/parser.h +19 -0
  27. data/ext/markly/plaintext.c +3 -0
  28. data/ext/markly/xml.c +19 -1
  29. data/lib/markly/flags.rb +10 -1
  30. data/lib/markly/node/inspect.rb +10 -3
  31. data/lib/markly/node.rb +53 -28
  32. data/lib/markly/renderer/generic.rb +42 -12
  33. data/lib/markly/renderer/headings.rb +24 -19
  34. data/lib/markly/renderer/html.rb +111 -10
  35. data/lib/markly/version.rb +4 -2
  36. data/lib/markly.rb +14 -13
  37. data/readme.md +22 -6
  38. data/releases.md +16 -0
  39. data.tar.gz.sig +0 -0
  40. metadata +23 -21
  41. metadata.gz.sig +0 -0
  42. /data/ext/markly/{autolink.c → extensions/autolink.c} +0 -0
  43. /data/ext/markly/{autolink.h → extensions/autolink.h} +0 -0
  44. /data/ext/markly/{cmark-gfm-core-extensions.h → extensions/cmark-gfm-core-extensions.h} +0 -0
  45. /data/ext/markly/{cmark-gfm-extensions_export.h → extensions/cmark-gfm-extensions_export.h} +0 -0
  46. /data/ext/markly/{core-extensions.c → extensions/core-extensions.c} +0 -0
  47. /data/ext/markly/{ext_scanners.c → extensions/ext_scanners.c} +0 -0
  48. /data/ext/markly/{ext_scanners.h → extensions/ext_scanners.h} +0 -0
  49. /data/ext/markly/{strikethrough.c → extensions/strikethrough.c} +0 -0
  50. /data/ext/markly/{strikethrough.h → extensions/strikethrough.h} +0 -0
  51. /data/ext/markly/{table.h → extensions/table.h} +0 -0
  52. /data/ext/markly/{tagfilter.c → extensions/tagfilter.c} +0 -0
  53. /data/ext/markly/{tagfilter.h → extensions/tagfilter.h} +0 -0
  54. /data/ext/markly/{tasklist.c → extensions/tasklist.c} +0 -0
  55. /data/ext/markly/{tasklist.h → extensions/tasklist.h} +0 -0
@@ -0,0 +1,141 @@
1
+ #include "front_matter.h"
2
+ #include "cmark-gfm.h"
3
+
4
+ #include <string.h>
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Delimiter and info string parsing
8
+ // ---------------------------------------------------------------------------
9
+
10
+ // Return true if `input` is an opening front matter delimiter: "---" followed
11
+ // by an optional info string and a newline. No leading whitespace before
12
+ // "---" is permitted.
13
+ //
14
+ // Note: some tools (e.g. Jekyll) also accept "..." as a closing delimiter,
15
+ // derived from the YAML document-end marker. We intentionally do not support
16
+ // it here because this implementation is format-agnostic — the content between
17
+ // the delimiters may be YAML, TOML, JSON, or anything else. "..." has no
18
+ // meaning outside of YAML, so "---" is the only unambiguous delimiter.
19
+ static bool is_opening_delimiter(cmark_chunk *input) {
20
+ const unsigned char *p = input->data;
21
+ if (input->len < 3 || !(p[0] == '-' && p[1] == '-' && p[2] == '-'))
22
+ return false;
23
+ // A fourth consecutive dash produces a longer thematic break, not a
24
+ // front matter delimiter.
25
+ if (input->len > 3 && p[3] == '-')
26
+ return false;
27
+ return true;
28
+ }
29
+
30
+ // Return true if `input` is a closing front matter delimiter: exactly "---"
31
+ // with optional trailing whitespace then a newline. An info string is not
32
+ // permitted on the closing delimiter.
33
+ static bool is_closing_delimiter(cmark_chunk *input) {
34
+ const unsigned char *p = input->data;
35
+ int len = input->len;
36
+
37
+ if (len < 3 || !(p[0] == '-' && p[1] == '-' && p[2] == '-'))
38
+ return false;
39
+
40
+ for (int i = 3; i < len; i++) {
41
+ if (p[i] == '\n' || p[i] == '\r')
42
+ return true;
43
+ if (p[i] != ' ' && p[i] != '\t')
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+
49
+ // Extract the optional info string from an opening delimiter line, e.g.
50
+ // "--- yaml\n" yields "yaml". Returns a zero-length chunk if absent.
51
+ static cmark_chunk parse_info(cmark_chunk *input) {
52
+ const unsigned char *p = input->data + 3;
53
+ int len = input->len - 3;
54
+
55
+ while (len > 0 && (*p == ' ' || *p == '\t')) { p++; len--; }
56
+ while (len > 0 && (p[len-1] == '\n' || p[len-1] == '\r' ||
57
+ p[len-1] == ' ' || p[len-1] == '\t'))
58
+ len--;
59
+
60
+ return (cmark_chunk){ .data = (unsigned char *)p,
61
+ .len = (bufsize_t)len,
62
+ .alloc = 0 };
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Node creation
67
+ // ---------------------------------------------------------------------------
68
+
69
+ static void create_front_matter_node(cmark_parser *parser) {
70
+ cmark_node *node =
71
+ cmark_node_new_with_mem(CMARK_NODE_FRONT_MATTER, parser->mem);
72
+
73
+ // Store identically to a code block: info string + literal content.
74
+ cmark_node_set_fence_info(node,
75
+ parser->front_matter_info.size > 0
76
+ ? (const char *)parser->front_matter_info.ptr
77
+ : "");
78
+
79
+ cmark_node_set_literal(node,
80
+ parser->front_matter_buf.size > 0
81
+ ? (const char *)parser->front_matter_buf.ptr
82
+ : "");
83
+
84
+ node->start_line = 1;
85
+ node->start_column = 1;
86
+ node->end_line = parser->line_number;
87
+ node->end_column = 3;
88
+
89
+ cmark_node *first = cmark_node_first_child(parser->root);
90
+ if (first)
91
+ cmark_node_insert_before(first, node);
92
+ else
93
+ cmark_node_append_child(parser->root, node);
94
+
95
+ parser->front_matter_scanning = false;
96
+ cmark_strbuf_clear(&parser->front_matter_buf);
97
+ cmark_strbuf_clear(&parser->front_matter_info);
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // State machine — called from S_process_line in blocks.c
102
+ // ---------------------------------------------------------------------------
103
+
104
+ bool cmark_front_matter_process_line(cmark_parser *parser, cmark_chunk *input) {
105
+ // NULL signals end-of-document: the whole document is the front matter.
106
+ if (input == NULL) {
107
+ create_front_matter_node(parser);
108
+ return true;
109
+ }
110
+
111
+ // Adjust for any offset already consumed (e.g. a UTF-8 BOM on line 1).
112
+ cmark_chunk adjusted = {
113
+ .data = input->data + parser->offset,
114
+ .len = input->len - parser->offset,
115
+ .alloc = 0,
116
+ };
117
+ input = &adjusted;
118
+
119
+ if (parser->line_number == 1) {
120
+ if (is_opening_delimiter(input)) {
121
+ parser->front_matter_scanning = true;
122
+ // Capture optional info string (e.g. "yaml" from "--- yaml\n").
123
+ cmark_chunk info = parse_info(input);
124
+ if (info.len > 0)
125
+ cmark_strbuf_put(&parser->front_matter_info, info.data, info.len);
126
+ }
127
+ return parser->front_matter_scanning;
128
+ }
129
+
130
+ if (!parser->front_matter_scanning)
131
+ return false;
132
+
133
+ if (is_closing_delimiter(input)) {
134
+ create_front_matter_node(parser);
135
+ return true;
136
+ }
137
+
138
+ // Accumulate this content line.
139
+ cmark_strbuf_put(&parser->front_matter_buf, input->data, input->len);
140
+ return true;
141
+ }
@@ -0,0 +1,24 @@
1
+ #ifndef CMARK_FRONT_MATTER_H
2
+ #define CMARK_FRONT_MATTER_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif
7
+
8
+ #include "cmark-gfm.h"
9
+ #include "parser.h"
10
+ #include "chunk.h"
11
+
12
+ // Called from S_process_line in blocks.c for every line when
13
+ // CMARK_OPT_FRONT_MATTER is set. Drives the front matter state machine
14
+ // stored directly on the parser (front_matter_scanning / front_matter_buf).
15
+ //
16
+ // Returns true if the line was consumed by the front matter scanner and
17
+ // should not be passed to the normal block parser.
18
+ bool cmark_front_matter_process_line(cmark_parser *parser, cmark_chunk *input);
19
+
20
+ #ifdef __cplusplus
21
+ }
22
+ #endif
23
+
24
+ #endif
data/ext/markly/html.c CHANGED
@@ -18,6 +18,34 @@ static void escape_html(cmark_strbuf *dest, const unsigned char *source,
18
18
  houdini_escape_html0(dest, source, length, 0);
19
19
  }
20
20
 
21
+ static bufsize_t first_code_info_tag(cmark_chunk *info) {
22
+ bufsize_t first_tag = 0;
23
+ while (first_tag < info->len && !cmark_isspace(info->data[first_tag])) {
24
+ first_tag += 1;
25
+ }
26
+ return first_tag;
27
+ }
28
+
29
+ static void render_code_info_attrs(cmark_strbuf *html, cmark_chunk *info,
30
+ int options, bool pre_lang) {
31
+ bufsize_t first_tag = first_code_info_tag(info);
32
+
33
+ if (pre_lang) {
34
+ cmark_strbuf_puts(html, " lang=\"");
35
+ } else {
36
+ cmark_strbuf_puts(html, " class=\"language-");
37
+ }
38
+
39
+ escape_html(html, info->data, first_tag);
40
+
41
+ if (first_tag < info->len && (options & CMARK_OPT_FULL_INFO_STRING)) {
42
+ cmark_strbuf_puts(html, "\" data-meta=\"");
43
+ escape_html(html, info->data + first_tag + 1, info->len - first_tag - 1);
44
+ }
45
+
46
+ cmark_strbuf_putc(html, '"');
47
+ }
48
+
21
49
  static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size_t len) {
22
50
  cmark_strbuf *html = renderer->html;
23
51
  cmark_llist *it;
@@ -125,7 +153,11 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
125
153
  case CMARK_NODE_TEXT:
126
154
  case CMARK_NODE_CODE:
127
155
  case CMARK_NODE_HTML_INLINE:
128
- escape_html(html, node->as.literal.data, node->as.literal.len);
156
+ if (node->type == CMARK_NODE_CODE) {
157
+ escape_html(html, node->as.code.literal.data, node->as.code.literal.len);
158
+ } else {
159
+ escape_html(html, node->as.literal.data, node->as.literal.len);
160
+ }
129
161
  break;
130
162
 
131
163
  case CMARK_NODE_LINEBREAK:
@@ -220,32 +252,17 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
220
252
  cmark_html_render_sourcepos(node, html, options);
221
253
  cmark_strbuf_puts(html, "><code>");
222
254
  } else {
223
- bufsize_t first_tag = 0;
224
- while (first_tag < node->as.code.info.len &&
225
- !cmark_isspace(node->as.code.info.data[first_tag])) {
226
- first_tag += 1;
227
- }
228
-
229
255
  if (options & CMARK_OPT_GITHUB_PRE_LANG) {
230
256
  cmark_strbuf_puts(html, "<pre");
231
257
  cmark_html_render_sourcepos(node, html, options);
232
- cmark_strbuf_puts(html, " lang=\"");
233
- escape_html(html, node->as.code.info.data, first_tag);
234
- if (first_tag < node->as.code.info.len && (options & CMARK_OPT_FULL_INFO_STRING)) {
235
- cmark_strbuf_puts(html, "\" data-meta=\"");
236
- escape_html(html, node->as.code.info.data + first_tag + 1, node->as.code.info.len - first_tag - 1);
237
- }
238
- cmark_strbuf_puts(html, "\"><code>");
258
+ render_code_info_attrs(html, &node->as.code.info, options, true);
259
+ cmark_strbuf_puts(html, "><code>");
239
260
  } else {
240
261
  cmark_strbuf_puts(html, "<pre");
241
262
  cmark_html_render_sourcepos(node, html, options);
242
- cmark_strbuf_puts(html, "><code class=\"language-");
243
- escape_html(html, node->as.code.info.data, first_tag);
244
- if (first_tag < node->as.code.info.len && (options & CMARK_OPT_FULL_INFO_STRING)) {
245
- cmark_strbuf_puts(html, "\" data-meta=\"");
246
- escape_html(html, node->as.code.info.data + first_tag + 1, node->as.code.info.len - first_tag - 1);
247
- }
248
- cmark_strbuf_puts(html, "\">");
263
+ cmark_strbuf_puts(html, "><code");
264
+ render_code_info_attrs(html, &node->as.code.info, options, false);
265
+ cmark_strbuf_putc(html, '>');
249
266
  }
250
267
  }
251
268
 
@@ -327,8 +344,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
327
344
  break;
328
345
 
329
346
  case CMARK_NODE_CODE:
330
- cmark_strbuf_puts(html, "<code>");
331
- escape_html(html, node->as.literal.data, node->as.literal.len);
347
+ cmark_strbuf_puts(html, "<code");
348
+ if (node->as.code.info.len > 0) {
349
+ render_code_info_attrs(html, &node->as.code.info, options, false);
350
+ }
351
+ cmark_strbuf_putc(html, '>');
352
+ escape_html(html, node->as.code.literal.data, node->as.code.literal.len);
332
353
  cmark_strbuf_puts(html, "</code>");
333
354
  break;
334
355
 
@@ -457,6 +478,9 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
457
478
  }
458
479
  break;
459
480
 
481
+ case CMARK_NODE_FRONT_MATTER:
482
+ break;
483
+
460
484
  default:
461
485
  assert(false);
462
486
  break;
data/ext/markly/inlines.c CHANGED
@@ -24,7 +24,6 @@ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99";
24
24
 
25
25
  // Macros for creating various kinds of simple.
26
26
  #define make_str(subj, sc, ec, s) make_literal(subj, CMARK_NODE_TEXT, sc, ec, s)
27
- #define make_code(subj, sc, ec, s) make_literal(subj, CMARK_NODE_CODE, sc, ec, s)
28
27
  #define make_raw_html(subj, sc, ec, s) make_literal(subj, CMARK_NODE_HTML_INLINE, sc, ec, s)
29
28
  #define make_linebreak(mem) make_simple(mem, CMARK_NODE_LINEBREAK)
30
29
  #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) {
104
103
  return e;
105
104
  }
106
105
 
106
+ static CMARK_INLINE cmark_node *make_code(subject *subj, int start_column,
107
+ int end_column, cmark_chunk literal) {
108
+ cmark_node *e = (cmark_node *)subj->mem->calloc(1, sizeof(*e));
109
+ cmark_strbuf_init(subj->mem, &e->content, 0);
110
+ e->type = CMARK_NODE_CODE;
111
+ e->as.code.literal = literal;
112
+ e->start_line = e->end_line = subj->line;
113
+ e->start_column = start_column + 1 + subj->column_offset + subj->block_offset;
114
+ e->end_column = end_column + 1 + subj->column_offset + subj->block_offset;
115
+ return e;
116
+ }
117
+
107
118
  // Like make_str, but parses entities.
108
119
  static cmark_node *make_str_with_entities(subject *subj,
109
120
  int start_column, int end_column,
@@ -385,6 +396,39 @@ static void S_normalize_code(cmark_strbuf *s) {
385
396
 
386
397
  }
387
398
 
399
+ static bufsize_t scan_inline_code_info_prefix(subject *subj, bufsize_t start) {
400
+ if (start >= subj->input.len ||
401
+ !cmark_is_inline_code_info_start_char((char)subj->input.data[start]) ||
402
+ (start > 0 && cmark_is_inline_code_info_char(
403
+ (char)subj->input.data[start - 1]))) {
404
+ return 0;
405
+ }
406
+
407
+ bufsize_t pos = start + 1;
408
+ while (pos < subj->input.len && cmark_is_inline_code_info_char(
409
+ (char)subj->input.data[pos])) {
410
+ pos++;
411
+ }
412
+
413
+ if (pos + 1 < subj->input.len && subj->input.data[pos] == ':' &&
414
+ subj->input.data[pos + 1] == '`') {
415
+ return pos;
416
+ }
417
+
418
+ return 0;
419
+ }
420
+
421
+ static bufsize_t find_inline_code_info_prefix(subject *subj, bufsize_t start,
422
+ bufsize_t limit) {
423
+ while (start < limit) {
424
+ if (scan_inline_code_info_prefix(subj, start)) {
425
+ return start;
426
+ }
427
+ start++;
428
+ }
429
+
430
+ return 0;
431
+ }
388
432
 
389
433
  // Parse backtick code section or raw backticks, return an inline.
390
434
  // Assumes that the subject has a backtick at the current position.
@@ -403,12 +447,35 @@ static cmark_node *handle_backticks(subject *subj, int options) {
403
447
  endpos - startpos - openticks.len);
404
448
  S_normalize_code(&buf);
405
449
 
406
- cmark_node *node = make_code(subj, startpos, endpos - openticks.len - 1, cmark_chunk_buf_detach(&buf));
407
- adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len, options);
450
+ cmark_node *node = make_code(subj, startpos, endpos - openticks.len - 1,
451
+ cmark_chunk_buf_detach(&buf));
452
+ adjust_subj_node_newlines(subj, node, endpos - startpos, openticks.len,
453
+ options);
408
454
  return node;
409
455
  }
410
456
  }
411
457
 
458
+ static cmark_node *handle_inline_code_info(subject *subj, int options) {
459
+ bufsize_t startpos = subj->pos;
460
+ bufsize_t colonpos = scan_inline_code_info_prefix(subj, startpos);
461
+ if (colonpos == 0) {
462
+ return NULL;
463
+ }
464
+
465
+ cmark_chunk info =
466
+ cmark_chunk_dup(&subj->input, startpos, colonpos - startpos);
467
+ subj->pos = colonpos + 1;
468
+
469
+ cmark_node *node = handle_backticks(subj, options);
470
+ if (node->type != CMARK_NODE_CODE) {
471
+ cmark_node_free(node);
472
+ subj->pos = startpos;
473
+ return NULL;
474
+ }
475
+
476
+ node->as.code.info = chunk_clone(subj->mem, &info);
477
+ return node;
478
+ }
412
479
 
413
480
  // Scan ***, **, or * and return number scanned, or 0.
414
481
  // Advances position.
@@ -1458,6 +1525,14 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent,
1458
1525
  if (c == 0) {
1459
1526
  return 0;
1460
1527
  }
1528
+ if (options & CMARK_OPT_INLINE_CODE_INFO) {
1529
+ new_inl = handle_inline_code_info(subj, options);
1530
+ }
1531
+ if (new_inl != NULL) {
1532
+ append_child(parent, new_inl);
1533
+ return 1;
1534
+ }
1535
+
1461
1536
  switch (c) {
1462
1537
  case '\r':
1463
1538
  case '\n':
@@ -1511,6 +1586,13 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent,
1511
1586
  break;
1512
1587
 
1513
1588
  endpos = subject_find_special_char(subj, options);
1589
+ if (options & CMARK_OPT_INLINE_CODE_INFO) {
1590
+ bufsize_t info_start =
1591
+ find_inline_code_info_prefix(subj, subj->pos + 1, endpos);
1592
+ if (info_start > 0 && info_start < endpos) {
1593
+ endpos = info_start;
1594
+ }
1595
+ }
1514
1596
  contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos);
1515
1597
  startpos = subj->pos;
1516
1598
  subj->pos = endpos;
@@ -139,10 +139,15 @@ void cmark_node_own(cmark_node *root) {
139
139
  switch (cur->type) {
140
140
  case CMARK_NODE_TEXT:
141
141
  case CMARK_NODE_HTML_INLINE:
142
- case CMARK_NODE_CODE:
143
142
  case CMARK_NODE_HTML_BLOCK:
144
143
  cmark_chunk_to_cstr(iter->mem, &cur->as.literal);
145
144
  break;
145
+ case CMARK_NODE_CODE:
146
+ cmark_chunk_to_cstr(iter->mem, &cur->as.code.literal);
147
+ if (cur->as.code.info.len > 0) {
148
+ cmark_chunk_to_cstr(iter->mem, &cur->as.code.info);
149
+ }
150
+ break;
146
151
  case CMARK_NODE_LINK:
147
152
  cmark_chunk_to_cstr(iter->mem, &cur->as.link.url);
148
153
  cmark_chunk_to_cstr(iter->mem, &cur->as.link.title);
data/ext/markly/latex.c CHANGED
@@ -451,6 +451,9 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
451
451
  // TODO
452
452
  break;
453
453
 
454
+ case CMARK_NODE_FRONT_MATTER:
455
+ break;
456
+
454
457
  default:
455
458
  assert(false);
456
459
  break;
data/ext/markly/man.c CHANGED
@@ -257,6 +257,9 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
257
257
  // TODO
258
258
  break;
259
259
 
260
+ case CMARK_NODE_FRONT_MATTER:
261
+ break;
262
+
260
263
  default:
261
264
  assert(false);
262
265
  break;