markly 0.16.0 → 0.18.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +36 -0
- data/ext/markly/cmark-gfm-extension_api.h +10 -0
- data/ext/markly/cmark-gfm.h +22 -0
- data/ext/markly/cmark-gfm_export.h +1 -0
- data/ext/markly/cmark-gfm_version.h +2 -2
- data/ext/markly/cmark_ctype.c +9 -0
- data/ext/markly/cmark_ctype.h +5 -0
- data/ext/markly/commonmark.c +10 -3
- data/ext/markly/config.h +0 -4
- data/ext/markly/extensions/table.c +24 -0
- data/ext/markly/html.c +47 -25
- data/ext/markly/inlines.c +85 -3
- data/ext/markly/iterator.c +6 -1
- data/ext/markly/markly.c +298 -201
- data/ext/markly/node.c +194 -3
- data/ext/markly/syntax_extension.c +5 -0
- data/ext/markly/syntax_extension.h +1 -0
- data/ext/markly/xml.c +12 -1
- data/lib/markly/flags.rb +4 -1
- data/lib/markly/node/inspect.rb +10 -3
- data/lib/markly/node.rb +54 -37
- data/lib/markly/renderer/generic.rb +42 -12
- data/lib/markly/renderer/headings.rb +24 -19
- data/lib/markly/renderer/html.rb +111 -10
- data/lib/markly/version.rb +3 -1
- data/lib/markly.rb +14 -13
- data/readme.md +15 -1
- data/releases.md +15 -1
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
data/ext/markly/node.c
CHANGED
|
@@ -145,16 +145,154 @@ cmark_node *cmark_node_new(cmark_node_type type) {
|
|
|
145
145
|
return cmark_node_new_with_ext(type, NULL);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
static cmark_chunk S_clone_chunk(cmark_mem *mem, const cmark_chunk *source) {
|
|
149
|
+
cmark_chunk clone = {NULL, source->len, 1};
|
|
150
|
+
clone.data = (unsigned char *)mem->calloc(source->len + 1, 1);
|
|
151
|
+
if (source->len > 0) {
|
|
152
|
+
memcpy(clone.data, source->data, source->len);
|
|
153
|
+
}
|
|
154
|
+
return clone;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static cmark_node *S_clone_node(cmark_node *node) {
|
|
158
|
+
cmark_mem *mem = NODE_MEM(node);
|
|
159
|
+
cmark_node *clone = cmark_node_new_with_mem_and_ext(
|
|
160
|
+
(cmark_node_type)node->type, mem, node->extension);
|
|
161
|
+
|
|
162
|
+
if (!clone) {
|
|
163
|
+
return NULL;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
cmark_strbuf_set(&clone->content, node->content.ptr, node->content.size);
|
|
167
|
+
clone->start_line = node->start_line;
|
|
168
|
+
clone->start_column = node->start_column;
|
|
169
|
+
clone->end_line = node->end_line;
|
|
170
|
+
clone->end_column = node->end_column;
|
|
171
|
+
clone->internal_offset = node->internal_offset;
|
|
172
|
+
clone->flags = node->flags;
|
|
173
|
+
clone->footnote = node->footnote;
|
|
174
|
+
|
|
175
|
+
switch (node->type) {
|
|
176
|
+
case CMARK_NODE_HEADING:
|
|
177
|
+
clone->as.heading = node->as.heading;
|
|
178
|
+
break;
|
|
179
|
+
case CMARK_NODE_LIST:
|
|
180
|
+
case CMARK_NODE_ITEM:
|
|
181
|
+
clone->as.list = node->as.list;
|
|
182
|
+
break;
|
|
183
|
+
case CMARK_NODE_CODE_BLOCK:
|
|
184
|
+
case CMARK_NODE_FRONT_MATTER:
|
|
185
|
+
case CMARK_NODE_CODE:
|
|
186
|
+
clone->as.code = node->as.code;
|
|
187
|
+
clone->as.code.info = S_clone_chunk(mem, &node->as.code.info);
|
|
188
|
+
clone->as.code.literal = S_clone_chunk(mem, &node->as.code.literal);
|
|
189
|
+
break;
|
|
190
|
+
case CMARK_NODE_TEXT:
|
|
191
|
+
case CMARK_NODE_HTML_INLINE:
|
|
192
|
+
case CMARK_NODE_HTML_BLOCK:
|
|
193
|
+
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
|
194
|
+
case CMARK_NODE_FOOTNOTE_DEFINITION:
|
|
195
|
+
clone->as.literal = S_clone_chunk(mem, &node->as.literal);
|
|
196
|
+
break;
|
|
197
|
+
case CMARK_NODE_LINK:
|
|
198
|
+
case CMARK_NODE_IMAGE:
|
|
199
|
+
clone->as.link.url = S_clone_chunk(mem, &node->as.link.url);
|
|
200
|
+
clone->as.link.title = S_clone_chunk(mem, &node->as.link.title);
|
|
201
|
+
break;
|
|
202
|
+
case CMARK_NODE_CUSTOM_BLOCK:
|
|
203
|
+
case CMARK_NODE_CUSTOM_INLINE:
|
|
204
|
+
clone->as.custom.on_enter = S_clone_chunk(mem, &node->as.custom.on_enter);
|
|
205
|
+
clone->as.custom.on_exit = S_clone_chunk(mem, &node->as.custom.on_exit);
|
|
206
|
+
break;
|
|
207
|
+
default:
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (node->extension && node->extension->opaque_alloc_func) {
|
|
212
|
+
if (!node->extension->opaque_copy_func) {
|
|
213
|
+
cmark_node_free(clone);
|
|
214
|
+
return NULL;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
node->extension->opaque_copy_func(node->extension, mem, clone, node);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (cmark_node *child = node->first_child; child; child = child->next) {
|
|
221
|
+
cmark_node *child_clone = S_clone_node(child);
|
|
222
|
+
if (!child_clone) {
|
|
223
|
+
cmark_node_free(clone);
|
|
224
|
+
return NULL;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!cmark_node_append_child(clone, child_clone)) {
|
|
228
|
+
cmark_node_free(child_clone);
|
|
229
|
+
cmark_node_free(clone);
|
|
230
|
+
return NULL;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return clone;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
static cmark_node *S_find_clone(cmark_node *source, cmark_node *clone,
|
|
238
|
+
cmark_node *target) {
|
|
239
|
+
if (source == target) {
|
|
240
|
+
return clone;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
cmark_node *source_child = source->first_child;
|
|
244
|
+
cmark_node *clone_child = clone->first_child;
|
|
245
|
+
while (source_child && clone_child) {
|
|
246
|
+
cmark_node *result = S_find_clone(source_child, clone_child, target);
|
|
247
|
+
if (result) {
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
source_child = source_child->next;
|
|
251
|
+
clone_child = clone_child->next;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return NULL;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static void S_clone_footnote_links(cmark_node *source, cmark_node *clone,
|
|
258
|
+
cmark_node *source_root,
|
|
259
|
+
cmark_node *clone_root) {
|
|
260
|
+
if (source->parent_footnote_def) {
|
|
261
|
+
clone->parent_footnote_def =
|
|
262
|
+
S_find_clone(source_root, clone_root, source->parent_footnote_def);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
cmark_node *source_child = source->first_child;
|
|
266
|
+
cmark_node *clone_child = clone->first_child;
|
|
267
|
+
while (source_child && clone_child) {
|
|
268
|
+
S_clone_footnote_links(source_child, clone_child, source_root, clone_root);
|
|
269
|
+
source_child = source_child->next;
|
|
270
|
+
clone_child = clone_child->next;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
cmark_node *cmark_node_clone(cmark_node *node) {
|
|
275
|
+
if (!node) {
|
|
276
|
+
return NULL;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
cmark_node *clone = S_clone_node(node);
|
|
280
|
+
if (clone) {
|
|
281
|
+
S_clone_footnote_links(node, clone, node, clone);
|
|
282
|
+
}
|
|
283
|
+
return clone;
|
|
284
|
+
}
|
|
285
|
+
|
|
148
286
|
static void free_node_as(cmark_node *node) {
|
|
149
287
|
switch (node->type) {
|
|
150
288
|
case CMARK_NODE_CODE_BLOCK:
|
|
151
289
|
case CMARK_NODE_FRONT_MATTER:
|
|
290
|
+
case CMARK_NODE_CODE:
|
|
152
291
|
cmark_chunk_free(NODE_MEM(node), &node->as.code.info);
|
|
153
292
|
cmark_chunk_free(NODE_MEM(node), &node->as.code.literal);
|
|
154
293
|
break;
|
|
155
294
|
case CMARK_NODE_TEXT:
|
|
156
295
|
case CMARK_NODE_HTML_INLINE:
|
|
157
|
-
case CMARK_NODE_CODE:
|
|
158
296
|
case CMARK_NODE_HTML_BLOCK:
|
|
159
297
|
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
|
160
298
|
case CMARK_NODE_FOOTNOTE_DEFINITION:
|
|
@@ -378,11 +516,11 @@ const char *cmark_node_get_literal(cmark_node *node) {
|
|
|
378
516
|
case CMARK_NODE_HTML_BLOCK:
|
|
379
517
|
case CMARK_NODE_TEXT:
|
|
380
518
|
case CMARK_NODE_HTML_INLINE:
|
|
381
|
-
case CMARK_NODE_CODE:
|
|
382
519
|
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
|
383
520
|
case CMARK_NODE_FOOTNOTE_DEFINITION:
|
|
384
521
|
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);
|
|
385
522
|
|
|
523
|
+
case CMARK_NODE_CODE:
|
|
386
524
|
case CMARK_NODE_CODE_BLOCK:
|
|
387
525
|
case CMARK_NODE_FRONT_MATTER:
|
|
388
526
|
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.literal);
|
|
@@ -403,11 +541,11 @@ int cmark_node_set_literal(cmark_node *node, const char *content) {
|
|
|
403
541
|
case CMARK_NODE_HTML_BLOCK:
|
|
404
542
|
case CMARK_NODE_TEXT:
|
|
405
543
|
case CMARK_NODE_HTML_INLINE:
|
|
406
|
-
case CMARK_NODE_CODE:
|
|
407
544
|
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
|
408
545
|
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.literal, content);
|
|
409
546
|
return 1;
|
|
410
547
|
|
|
548
|
+
case CMARK_NODE_CODE:
|
|
411
549
|
case CMARK_NODE_CODE_BLOCK:
|
|
412
550
|
case CMARK_NODE_FRONT_MATTER:
|
|
413
551
|
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
|
|
@@ -595,6 +733,59 @@ int cmark_node_set_item_index(cmark_node *node, int idx) {
|
|
|
595
733
|
}
|
|
596
734
|
}
|
|
597
735
|
|
|
736
|
+
const char *cmark_node_get_code_info(cmark_node *node) {
|
|
737
|
+
if (node == NULL) {
|
|
738
|
+
return NULL;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (node->type == CMARK_NODE_CODE ||
|
|
742
|
+
node->type == CMARK_NODE_CODE_BLOCK ||
|
|
743
|
+
node->type == CMARK_NODE_FRONT_MATTER) {
|
|
744
|
+
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.info);
|
|
745
|
+
} else {
|
|
746
|
+
return NULL;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static int valid_inline_code_info(const char *info) {
|
|
751
|
+
if (info == NULL || *info == '\0') {
|
|
752
|
+
return 1;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (!cmark_is_inline_code_info_start_char(*info)) {
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
while (*++info) {
|
|
760
|
+
if (!cmark_is_inline_code_info_char(*info)) {
|
|
761
|
+
return 0;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return 1;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
int cmark_node_set_code_info(cmark_node *node, const char *info) {
|
|
769
|
+
if (node == NULL) {
|
|
770
|
+
return 0;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (node->type == CMARK_NODE_CODE) {
|
|
774
|
+
if (!valid_inline_code_info(info)) {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
|
|
779
|
+
return 1;
|
|
780
|
+
} else if (node->type == CMARK_NODE_CODE_BLOCK ||
|
|
781
|
+
node->type == CMARK_NODE_FRONT_MATTER) {
|
|
782
|
+
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
|
|
783
|
+
return 1;
|
|
784
|
+
} else {
|
|
785
|
+
return 0;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
598
789
|
const char *cmark_node_get_fence_info(cmark_node *node) {
|
|
599
790
|
if (node == NULL) {
|
|
600
791
|
return NULL;
|
|
@@ -143,6 +143,11 @@ void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extensi
|
|
|
143
143
|
extension->opaque_free_func = func;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
void cmark_syntax_extension_set_opaque_copy_func(
|
|
147
|
+
cmark_syntax_extension *extension, cmark_opaque_copy_func func) {
|
|
148
|
+
extension->opaque_copy_func = func;
|
|
149
|
+
}
|
|
150
|
+
|
|
146
151
|
void cmark_syntax_extension_set_commonmark_escape_func(cmark_syntax_extension *extension,
|
|
147
152
|
cmark_commonmark_escape_func func) {
|
|
148
153
|
extension->commonmark_escape_func = func;
|
|
@@ -28,6 +28,7 @@ struct cmark_syntax_extension {
|
|
|
28
28
|
cmark_postprocess_func postprocess_func;
|
|
29
29
|
cmark_opaque_alloc_func opaque_alloc_func;
|
|
30
30
|
cmark_opaque_free_func opaque_free_func;
|
|
31
|
+
cmark_opaque_copy_func opaque_copy_func;
|
|
31
32
|
cmark_commonmark_escape_func commonmark_escape_func;
|
|
32
33
|
};
|
|
33
34
|
|
data/ext/markly/xml.c
CHANGED
|
@@ -72,7 +72,6 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
|
|
|
72
72
|
literal = true;
|
|
73
73
|
break;
|
|
74
74
|
case CMARK_NODE_TEXT:
|
|
75
|
-
case CMARK_NODE_CODE:
|
|
76
75
|
case CMARK_NODE_HTML_BLOCK:
|
|
77
76
|
case CMARK_NODE_HTML_INLINE:
|
|
78
77
|
cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
|
|
@@ -81,6 +80,18 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
|
|
|
81
80
|
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
|
82
81
|
literal = true;
|
|
83
82
|
break;
|
|
83
|
+
case CMARK_NODE_CODE:
|
|
84
|
+
if (node->as.code.info.len > 0) {
|
|
85
|
+
cmark_strbuf_puts(xml, " info=\"");
|
|
86
|
+
escape_xml(xml, node->as.code.info.data, node->as.code.info.len);
|
|
87
|
+
cmark_strbuf_putc(xml, '"');
|
|
88
|
+
}
|
|
89
|
+
cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
|
|
90
|
+
escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len);
|
|
91
|
+
cmark_strbuf_puts(xml, "</");
|
|
92
|
+
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
|
93
|
+
literal = true;
|
|
94
|
+
break;
|
|
84
95
|
case CMARK_NODE_LIST:
|
|
85
96
|
switch (cmark_node_get_list_type(node)) {
|
|
86
97
|
case CMARK_ORDERED_LIST:
|
data/lib/markly/flags.rb
CHANGED
|
@@ -20,12 +20,15 @@ module Markly
|
|
|
20
20
|
UNSAFE = 1 << 17
|
|
21
21
|
# Parse front matter ("---" delimited block at start of document).
|
|
22
22
|
# The raw content is available via node.string_content and the optional
|
|
23
|
-
# format hint (e.g. "yaml", "toml") via node.
|
|
23
|
+
# format hint (e.g. "yaml", "toml") via node.code_info; interpretation
|
|
24
24
|
# is left to the caller.
|
|
25
25
|
FRONT_MATTER = 1 << 18
|
|
26
|
+
# Parse language prefixes on inline code spans, e.g. ruby:`Object.new`.
|
|
27
|
+
INLINE_CODE_INFO = 1 << 19
|
|
26
28
|
|
|
27
29
|
PARSE_FLAGS = {
|
|
28
30
|
front_matter: FRONT_MATTER,
|
|
31
|
+
inline_code_info: INLINE_CODE_INFO,
|
|
29
32
|
validate_utf8: VALIDATE_UTF8,
|
|
30
33
|
smart_quotes: SMART,
|
|
31
34
|
liberal_html_tags: LIBERAL_HTML_TAG,
|
data/lib/markly/node/inspect.rb
CHANGED
|
@@ -4,20 +4,27 @@
|
|
|
4
4
|
# Copyright, 2017, by Goro Fuji.
|
|
5
5
|
# Copyright, 2017-2019, by Garen Torikian.
|
|
6
6
|
# Copyright, 2020, by Olle Jonsson.
|
|
7
|
-
# Copyright, 2020-
|
|
7
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
8
8
|
|
|
9
9
|
require "pp"
|
|
10
10
|
|
|
11
11
|
module Markly
|
|
12
12
|
class Node
|
|
13
|
+
# Provides concise pretty-printing for Markdown nodes.
|
|
13
14
|
module Inspect
|
|
15
|
+
# @constant [Integer] The indentation width used for nested node attributes.
|
|
14
16
|
PP_INDENT_SIZE = 2
|
|
15
17
|
|
|
18
|
+
# Returns a pretty-printed representation of the node.
|
|
19
|
+
#
|
|
20
|
+
# @returns [String] The formatted node representation.
|
|
16
21
|
def inspect
|
|
17
22
|
PP.pp(self, +"", Float::INFINITY)
|
|
18
23
|
end
|
|
19
24
|
|
|
20
|
-
#
|
|
25
|
+
# Pretty-print this node and its children.
|
|
26
|
+
#
|
|
27
|
+
# @parameter printer [PrettyPrint] The pretty-print formatter.
|
|
21
28
|
def pretty_print(printer)
|
|
22
29
|
printer.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", ">") do
|
|
23
30
|
printer.breakable
|
|
@@ -31,7 +38,7 @@ module Markly
|
|
|
31
38
|
list_type
|
|
32
39
|
list_start
|
|
33
40
|
list_tight
|
|
34
|
-
|
|
41
|
+
code_info
|
|
35
42
|
].map do |name|
|
|
36
43
|
begin
|
|
37
44
|
[name, __send__(name)]
|
data/lib/markly/node.rb
CHANGED
|
@@ -5,32 +5,29 @@
|
|
|
5
5
|
# Copyright, 2016-2017, by Yuki Izumi.
|
|
6
6
|
# Copyright, 2017, by Goro Fuji.
|
|
7
7
|
# Copyright, 2018, by Jerry van Leeuwen.
|
|
8
|
-
# Copyright, 2020-
|
|
8
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
9
9
|
# Copyright, 2025, by Olle Jonsson.
|
|
10
10
|
|
|
11
11
|
require_relative "node/inspect"
|
|
12
12
|
|
|
13
13
|
module Markly
|
|
14
|
+
# Represents a node in a parsed Markdown document tree.
|
|
14
15
|
class Node
|
|
15
16
|
include Enumerable
|
|
16
17
|
include Inspect
|
|
17
18
|
|
|
18
|
-
# Duplicate the current node and all children.
|
|
19
|
+
# Duplicate the current node and all its children.
|
|
20
|
+
#
|
|
21
|
+
# @returns [Markly::Node] The duplicated node tree.
|
|
19
22
|
def dup
|
|
20
|
-
|
|
21
|
-
node = Markly.parse(self.to_markdown)
|
|
22
|
-
|
|
23
|
-
# If we aren't duplicating a document, we return `first_child` as the root will be a document node:
|
|
24
|
-
if self.type == :document
|
|
25
|
-
return node
|
|
26
|
-
else
|
|
27
|
-
return node.first_child
|
|
28
|
-
end
|
|
23
|
+
_dup
|
|
29
24
|
end
|
|
30
25
|
|
|
31
|
-
#
|
|
26
|
+
# Walk the node tree recursively.
|
|
32
27
|
#
|
|
33
|
-
#
|
|
28
|
+
# @yields {|node| ...} Each node in depth-first order, including this node.
|
|
29
|
+
# @parameter node [Markly::Node] The current node.
|
|
30
|
+
# @returns [Enumerator | Nil] An enumerator when no block is given.
|
|
34
31
|
def walk(&block)
|
|
35
32
|
return enum_for(:walk) unless block_given?
|
|
36
33
|
|
|
@@ -40,39 +37,47 @@ module Markly
|
|
|
40
37
|
end
|
|
41
38
|
end
|
|
42
39
|
|
|
43
|
-
#
|
|
40
|
+
# Convert the node to an HTML string.
|
|
44
41
|
#
|
|
45
|
-
# flags
|
|
46
|
-
# extensions
|
|
47
|
-
#
|
|
48
|
-
# Returns a {String}.
|
|
42
|
+
# @parameter flags [Integer] The enabled rendering flags.
|
|
43
|
+
# @parameter extensions [Array(Symbol)] The extensions to enable.
|
|
44
|
+
# @returns [String] The rendered HTML.
|
|
49
45
|
def to_html(flags: DEFAULT, extensions: [])
|
|
50
46
|
_render_html(flags, extensions).force_encoding("utf-8")
|
|
51
47
|
end
|
|
52
48
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# flags - A {Symbol} or {Array of Symbol}s indicating the render options
|
|
56
|
-
# width - Column to wrap the output at
|
|
49
|
+
# Convert the node to a CommonMark string.
|
|
57
50
|
#
|
|
58
|
-
#
|
|
51
|
+
# @parameter flags [Integer] The enabled rendering flags.
|
|
52
|
+
# @parameter width [Integer] The column at which to wrap output, or `0` to disable wrapping.
|
|
53
|
+
# @returns [String] The rendered CommonMark text.
|
|
59
54
|
def to_commonmark(flags: DEFAULT, width: 0)
|
|
60
55
|
_render_commonmark(flags, width).force_encoding("utf-8")
|
|
61
56
|
end
|
|
62
57
|
|
|
63
58
|
alias to_markdown to_commonmark
|
|
64
59
|
|
|
65
|
-
#
|
|
60
|
+
# Return the language identifier from the code info string.
|
|
66
61
|
#
|
|
67
|
-
#
|
|
68
|
-
|
|
62
|
+
# @returns [String | Nil] The language identifier, or `nil` when none is present.
|
|
63
|
+
def code_language
|
|
64
|
+
code_info.split(/\s+/, 2).first
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Convert the node to a plain-text string.
|
|
69
68
|
#
|
|
70
|
-
#
|
|
69
|
+
# @parameter flags [Integer] The enabled rendering flags.
|
|
70
|
+
# @parameter width [Integer] The column at which to wrap output, or `0` to disable wrapping.
|
|
71
|
+
# @returns [String] The rendered plain text.
|
|
71
72
|
def to_plaintext(flags: DEFAULT, width: 0)
|
|
72
73
|
_render_plaintext(flags, width).force_encoding("utf-8")
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
#
|
|
76
|
+
# Iterate over the direct children of this node.
|
|
77
|
+
#
|
|
78
|
+
# @yields {|child| ...} Each direct child of this node.
|
|
79
|
+
# @parameter child [Markly::Node] The current child node.
|
|
80
|
+
# @returns [Enumerator | Nil] An enumerator when no block is given.
|
|
76
81
|
def each
|
|
77
82
|
return enum_for(:each) unless block_given?
|
|
78
83
|
|
|
@@ -84,6 +89,10 @@ module Markly
|
|
|
84
89
|
end
|
|
85
90
|
end
|
|
86
91
|
|
|
92
|
+
# Finds a direct child header with the given text.
|
|
93
|
+
#
|
|
94
|
+
# @parameter title [String] The header text to match.
|
|
95
|
+
# @returns [Markly::Node | Nil] The matching header, if present.
|
|
87
96
|
def find_header(title)
|
|
88
97
|
each do |child|
|
|
89
98
|
if child.type == :header && child.first_child.string_content == title
|
|
@@ -94,7 +103,9 @@ module Markly
|
|
|
94
103
|
|
|
95
104
|
# Delete all nodes until the block returns true.
|
|
96
105
|
#
|
|
97
|
-
# @
|
|
106
|
+
# @yields {|node| ...} Each node before it is deleted.
|
|
107
|
+
# @parameter node [Markly::Node] The current node.
|
|
108
|
+
# @returns [Markly::Node | Nil] The node for which the block returned `true`, if any.
|
|
98
109
|
def delete_until
|
|
99
110
|
current = self
|
|
100
111
|
while current
|
|
@@ -107,9 +118,9 @@ module Markly
|
|
|
107
118
|
|
|
108
119
|
# Replace a section (header + content) with a new node.
|
|
109
120
|
#
|
|
110
|
-
# @parameter new_node [Markly::Node]
|
|
111
|
-
# @parameter replace_header [Boolean]
|
|
112
|
-
# @parameter remove_subsections [Boolean]
|
|
121
|
+
# @parameter new_node [Markly::Node | Nil] The node with which to replace the section.
|
|
122
|
+
# @parameter replace_header [Boolean] Whether to replace the header itself.
|
|
123
|
+
# @parameter remove_subsections [Boolean] Whether to remove subsections.
|
|
113
124
|
def replace_section(new_node, replace_header: true, remove_subsections: true)
|
|
114
125
|
# Delete until the next heading:
|
|
115
126
|
self.next&.delete_until do |node|
|
|
@@ -120,21 +131,27 @@ module Markly
|
|
|
120
131
|
self.delete if replace_header
|
|
121
132
|
end
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
# Finds the next sibling header.
|
|
135
|
+
#
|
|
136
|
+
# @returns [Markly::Node | Nil] The next header, if present.
|
|
137
|
+
def next_header
|
|
124
138
|
current = self.next
|
|
125
139
|
while current
|
|
126
|
-
if current.type == :
|
|
140
|
+
if current.type == :header
|
|
127
141
|
return current
|
|
128
142
|
end
|
|
129
143
|
current = current.next
|
|
130
144
|
end
|
|
131
145
|
end
|
|
132
146
|
|
|
147
|
+
# An alias for {ruby Markly::Node#next_header}.
|
|
148
|
+
alias next_heading next_header
|
|
149
|
+
|
|
133
150
|
# Append the given node after the current node.
|
|
134
151
|
#
|
|
135
152
|
# It's okay to provide a document node, its children will be appended.
|
|
136
153
|
#
|
|
137
|
-
# @parameter node [Markly::Node]
|
|
154
|
+
# @parameter node [Markly::Node] The node to append.
|
|
138
155
|
def append_after(node)
|
|
139
156
|
if node.type == :document
|
|
140
157
|
node = node.first_child
|
|
@@ -153,7 +170,7 @@ module Markly
|
|
|
153
170
|
#
|
|
154
171
|
# It's okay to provide a document node, its children will be appended.
|
|
155
172
|
#
|
|
156
|
-
# @parameter node [Markly::Node]
|
|
173
|
+
# @parameter node [Markly::Node] The node to append.
|
|
157
174
|
def append_before(node)
|
|
158
175
|
if node.type == :document
|
|
159
176
|
node = node.first_child
|
|
@@ -169,7 +186,7 @@ module Markly
|
|
|
169
186
|
|
|
170
187
|
# Extract the children as a fragment.
|
|
171
188
|
#
|
|
172
|
-
# @returns [Markly::Node]
|
|
189
|
+
# @returns [Markly::Node] The fragment.
|
|
173
190
|
def extract_children
|
|
174
191
|
fragment = Markly::Node.new(:custom_inline)
|
|
175
192
|
|
|
@@ -3,26 +3,37 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2015-2019, by Garen Torikian.
|
|
5
5
|
# Copyright, 2016-2017, by Yuki Izumi.
|
|
6
|
-
# Copyright, 2020-
|
|
6
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
7
7
|
|
|
8
8
|
require "set"
|
|
9
9
|
require "stringio"
|
|
10
10
|
|
|
11
11
|
module Markly
|
|
12
|
+
# @namespace
|
|
12
13
|
module Renderer
|
|
14
|
+
# Base class for renderers implemented in Ruby.
|
|
13
15
|
class Generic
|
|
16
|
+
# Initializes a renderer with rendering flags and extensions.
|
|
17
|
+
#
|
|
18
|
+
# @parameter flags [Integer] The enabled rendering flags.
|
|
19
|
+
# @parameter extensions [Array(Symbol)] The enabled extensions.
|
|
14
20
|
def initialize(flags: DEFAULT, extensions: [])
|
|
15
21
|
@flags = flags
|
|
16
22
|
@stream = StringIO.new(+"")
|
|
17
|
-
@need_blocksep = false
|
|
18
23
|
@in_tight = false
|
|
19
24
|
@in_plain = false
|
|
20
25
|
@tagfilter = extensions.include?(:tagfilter)
|
|
21
26
|
end
|
|
22
27
|
|
|
28
|
+
# @attribute [Boolean] Whether the renderer is inside a tight container.
|
|
23
29
|
attr_accessor :in_tight
|
|
30
|
+
|
|
31
|
+
# @attribute [Boolean] Whether the renderer is emitting plain text.
|
|
24
32
|
attr_accessor :in_plain
|
|
25
33
|
|
|
34
|
+
# Writes strings, nodes, arrays of nodes, or child-node markers to the output.
|
|
35
|
+
#
|
|
36
|
+
# @parameter args [Array(Object)] Values to append or render.
|
|
26
37
|
def out(*args)
|
|
27
38
|
args.each do |arg|
|
|
28
39
|
if arg == :children
|
|
@@ -37,6 +48,10 @@ module Markly
|
|
|
37
48
|
end
|
|
38
49
|
end
|
|
39
50
|
|
|
51
|
+
# Renders a node and returns the completed output for document nodes.
|
|
52
|
+
#
|
|
53
|
+
# @parameter node [Markly::Node] The node to render.
|
|
54
|
+
# @returns [String | Nil] The output string when rendering a document.
|
|
40
55
|
def render(node)
|
|
41
56
|
@node = node
|
|
42
57
|
if node.type == :document
|
|
@@ -49,42 +64,57 @@ module Markly
|
|
|
49
64
|
end
|
|
50
65
|
end
|
|
51
66
|
|
|
67
|
+
# Renders a document node and all of its children.
|
|
68
|
+
#
|
|
69
|
+
# @parameter _node [Markly::Node] The document node.
|
|
52
70
|
def document(_node)
|
|
53
71
|
out(:children)
|
|
54
72
|
end
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
# Renders a code block node.
|
|
75
|
+
#
|
|
76
|
+
# Subclasses should override this callback.
|
|
77
|
+
# @parameter _node [Markly::Node] The code block node.
|
|
78
|
+
def code_block(_node)
|
|
79
|
+
raise NotImplementedError, "#{self.class} must implement #code_block"
|
|
58
80
|
end
|
|
59
81
|
|
|
82
|
+
# Ignores reference-definition nodes, which have no direct output.
|
|
83
|
+
#
|
|
84
|
+
# @parameter _node [Markly::Node] The reference-definition node.
|
|
60
85
|
def reference_def(_node); end
|
|
61
86
|
|
|
87
|
+
# Writes a newline unless the output is empty or already ends with one.
|
|
88
|
+
#
|
|
62
89
|
def cr
|
|
63
90
|
return if @stream.string.empty? || @stream.string[-1] == "\n"
|
|
64
91
|
|
|
65
92
|
out("\n")
|
|
66
93
|
end
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def containersep
|
|
73
|
-
cr unless @in_tight
|
|
74
|
-
end
|
|
75
|
-
|
|
95
|
+
# Renders a block surrounded by normalized newlines.
|
|
96
|
+
#
|
|
97
|
+
# @yields {|| ...} The block content to render.
|
|
76
98
|
def block
|
|
77
99
|
cr
|
|
78
100
|
yield
|
|
79
101
|
cr
|
|
80
102
|
end
|
|
81
103
|
|
|
104
|
+
# Renders content between opening and closing strings.
|
|
105
|
+
#
|
|
106
|
+
# @parameter starter [String] The opening output.
|
|
107
|
+
# @parameter ender [String] The closing output.
|
|
108
|
+
# @yields {|| ...} The container content to render.
|
|
82
109
|
def container(starter, ender)
|
|
83
110
|
out(starter)
|
|
84
111
|
yield
|
|
85
112
|
out(ender)
|
|
86
113
|
end
|
|
87
114
|
|
|
115
|
+
# Renders a block in plain-text mode, suppressing structural markup.
|
|
116
|
+
#
|
|
117
|
+
# @yields {|| ...} The content to render as plain text.
|
|
88
118
|
def plain
|
|
89
119
|
old_in_plain = @in_plain
|
|
90
120
|
@in_plain = true
|