commonmarker 0.21.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of commonmarker might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +82 -11
- data/bin/commonmarker +33 -8
- data/commonmarker.gemspec +2 -3
- data/ext/commonmarker/commonmarker.c +69 -2
- data/lib/commonmarker/config.rb +34 -40
- data/lib/commonmarker/node/inspect.rb +4 -6
- data/lib/commonmarker/node.rb +11 -1
- data/lib/commonmarker/renderer/html_renderer.rb +1 -1
- data/lib/commonmarker/renderer.rb +6 -4
- data/lib/commonmarker/version.rb +1 -1
- data/test/benchmark.rb +0 -4
- data/test/test_commands.rb +51 -10
- data/test/test_commonmark.rb +2 -2
- data/test/test_doc.rb +29 -29
- data/test/test_encoding.rb +6 -3
- data/test/test_extensions.rb +20 -20
- data/test/test_footnotes.rb +21 -0
- data/test/test_helper.rb +2 -2
- data/test/test_maliciousness.rb +2 -2
- data/test/test_pathological_inputs.rb +10 -10
- data/test/test_renderer.rb +17 -0
- data/test/test_xml.rb +107 -0
- metadata +15 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c52b70b0c680aae2d46b089b673745cbe586f1e27589bd81b1f3f0f091a7659d
|
4
|
+
data.tar.gz: 400efb13ab3ba9c72d451be4cd4cac8ee17688f70733d6d57771a093e412b63f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99309c1ab3c30c524adac6be1f75b3e63fe166b9248f8a8e74bd228a2428b10c794a56a3d5390786e4b33a869531f5879461c73d1ae7aaea66be491000779761
|
7
|
+
data.tar.gz: 2c4ac9a297cfb0204eb4355f0aa3e8471ddd2f6bf9162e7db2a71f16e32f3470779f86082e8aa9473ddf0f45ee3df41d133a7f073209c3d1441da4468266297d
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# CommonMarker
|
2
2
|
|
3
|
-
|
3
|
+
![Build Status](https://github.com/gjtorikian/commonmarker/workflows/CI/badge.svg) [![Gem Version](https://badge.fury.io/rb/commonmarker.svg)](http://badge.fury.io/rb/commonmarker)
|
4
4
|
|
5
5
|
Ruby wrapper for [libcmark-gfm](https://github.com/github/cmark),
|
6
6
|
GitHub's fork of the reference parser for CommonMark. It passes all of the C tests, and is therefore spec-complete. It also includes extensions to the CommonMark spec as documented in the [GitHub Flavored Markdown spec](http://github.github.com/gfm/), such as support for tables, strikethroughs, and autolinking.
|
@@ -139,16 +139,17 @@ CommonMarker accepts the same options that CMark does, as symbols. Note that the
|
|
139
139
|
|
140
140
|
### Render options
|
141
141
|
|
142
|
-
| Name | Description
|
143
|
-
| ------------------ | -----------
|
144
|
-
| `:DEFAULT` | The default rendering system.
|
145
|
-
| `:UNSAFE` | Allow raw/custom HTML and unsafe links.
|
146
|
-
| `:GITHUB_PRE_LANG` | Use GitHub-style `<pre lang>` for fenced code blocks.
|
147
|
-
| `:HARDBREAKS` | Treat `\n` as hardbreaks (by adding `<br/>`).
|
148
|
-
| `:NOBREAKS` | Translate `\n` in the source to a single whitespace.
|
149
|
-
| `:SOURCEPOS` | Include source position in rendered HTML.
|
150
|
-
| `:TABLE_PREFER_STYLE_ATTRIBUTES` | Use `style` insted of `align` for table cells |
|
151
|
-
| `:FULL_INFO_STRING` | Include full info strings of code blocks in separate attribute |
|
142
|
+
| Name | Description |
|
143
|
+
| ------------------ | ----------- |
|
144
|
+
| `:DEFAULT` | The default rendering system. |
|
145
|
+
| `:UNSAFE` | Allow raw/custom HTML and unsafe links. |
|
146
|
+
| `:GITHUB_PRE_LANG` | Use GitHub-style `<pre lang>` for fenced code blocks. |
|
147
|
+
| `:HARDBREAKS` | Treat `\n` as hardbreaks (by adding `<br/>`). |
|
148
|
+
| `:NOBREAKS` | Translate `\n` in the source to a single whitespace. |
|
149
|
+
| `:SOURCEPOS` | Include source position in rendered HTML. |
|
150
|
+
| `:TABLE_PREFER_STYLE_ATTRIBUTES` | Use `style` insted of `align` for table cells. |
|
151
|
+
| `:FULL_INFO_STRING` | Include full info strings of code blocks in separate attribute. |
|
152
|
+
| `:FOOTNOTES` | Render footnotes. |
|
152
153
|
|
153
154
|
### Passing options
|
154
155
|
|
@@ -179,6 +180,76 @@ The available extensions are:
|
|
179
180
|
* `:autolink` - This provides support for automatically converting URLs to anchor tags.
|
180
181
|
* `:tagfilter` - This escapes [several "unsafe" HTML tags](https://github.github.com/gfm/#disallowed-raw-html-extension-), causing them to not have any effect.
|
181
182
|
|
183
|
+
## Output formats
|
184
|
+
|
185
|
+
Like CMark, CommonMarker can generate output in several formats: HTML, XML, plaintext, and commonmark are currently supported.
|
186
|
+
|
187
|
+
### HTML
|
188
|
+
|
189
|
+
The default output format, HTML, will be generated when calling `to_html` or using `--to=html` on the command line.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
|
193
|
+
puts(doc.to_html)
|
194
|
+
|
195
|
+
<p><em>Hello</em> world!</p>
|
196
|
+
```
|
197
|
+
|
198
|
+
### XML
|
199
|
+
|
200
|
+
XML will be generated when calling `to_xml` or using `--to=xml` on the command line.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
|
204
|
+
puts(doc.to_xml)
|
205
|
+
|
206
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
207
|
+
<!DOCTYPE document SYSTEM "CommonMark.dtd">
|
208
|
+
<document xmlns="http://commonmark.org/xml/1.0">
|
209
|
+
<paragraph>
|
210
|
+
<emph>
|
211
|
+
<text xml:space="preserve">Hello</text>
|
212
|
+
</emph>
|
213
|
+
<text xml:space="preserve"> world!</text>
|
214
|
+
</paragraph>
|
215
|
+
</document>
|
216
|
+
```
|
217
|
+
|
218
|
+
### Plaintext
|
219
|
+
|
220
|
+
Plaintext will be generated when calling `to_plaintext` or using `--to=plaintext` on the command line.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
|
224
|
+
puts(doc.to_plaintext)
|
225
|
+
|
226
|
+
Hello world!
|
227
|
+
```
|
228
|
+
|
229
|
+
### Commonmark
|
230
|
+
|
231
|
+
Commonmark will be generated when calling `to_commonmark` or using `--to=commonmark` on the command line.
|
232
|
+
|
233
|
+
``` ruby
|
234
|
+
text = <<-TEXT
|
235
|
+
1. I am a numeric list.
|
236
|
+
2. I continue the list.
|
237
|
+
* Suddenly, an unordered list!
|
238
|
+
* What fun!
|
239
|
+
TEXT
|
240
|
+
|
241
|
+
doc = CommonMarker.render_doc(text, :DEFAULT)
|
242
|
+
puts(doc.to_commonmark)
|
243
|
+
|
244
|
+
1. I am a numeric list.
|
245
|
+
2. I continue the list.
|
246
|
+
|
247
|
+
<!-- end list -->
|
248
|
+
|
249
|
+
- Suddenly, an unordered list\!
|
250
|
+
- What fun\!
|
251
|
+
```
|
252
|
+
|
182
253
|
## Developing locally
|
183
254
|
|
184
255
|
After cloning the repo:
|
data/bin/commonmarker
CHANGED
@@ -13,15 +13,18 @@ $LOAD_PATH.unshift File.expand_path('lib', root)
|
|
13
13
|
def parse_options
|
14
14
|
options = OpenStruct.new
|
15
15
|
extensions = CommonMarker.extensions
|
16
|
-
parse_options = CommonMarker::Config::
|
17
|
-
render_options = CommonMarker::Config::
|
16
|
+
parse_options = CommonMarker::Config::OPTS.fetch(:parse)
|
17
|
+
render_options = CommonMarker::Config::OPTS.fetch(:render)
|
18
|
+
format_options = CommonMarker::Config::OPTS.fetch(:format)
|
18
19
|
|
19
20
|
options.active_extensions = []
|
20
21
|
options.active_parse_options = [:DEFAULT]
|
21
22
|
options.active_render_options = [:DEFAULT]
|
23
|
+
options.output_format = :html
|
22
24
|
|
23
25
|
option_parser = OptionParser.new do |opts|
|
24
26
|
opts.banner = 'Usage: commonmarker [--html-renderer] [--extension=EXTENSION]'
|
27
|
+
opts.separator ' [--to=FORMAT]'
|
25
28
|
opts.separator ' [--parse-option=OPTION]'
|
26
29
|
opts.separator ' [--render-option=OPTION]'
|
27
30
|
opts.separator ' [FILE..]'
|
@@ -43,6 +46,7 @@ def parse_options
|
|
43
46
|
opts.on('-h', '--help', 'Prints this help') do
|
44
47
|
puts opts
|
45
48
|
puts
|
49
|
+
puts "Available formats: #{format_options.join(', ')}"
|
46
50
|
puts "Available extentions: #{extensions.join(', ')}"
|
47
51
|
puts "Available parse options: #{parse_options.keys.join(', ')}"
|
48
52
|
puts "Available render options: #{render_options.keys.join(', ')}"
|
@@ -51,7 +55,16 @@ def parse_options
|
|
51
55
|
exit
|
52
56
|
end
|
53
57
|
|
54
|
-
opts.on('
|
58
|
+
opts.on('-tFORMAT', '--to=FORMAT', String, 'Specify output FORMAT') do |value|
|
59
|
+
value = value.to_sym
|
60
|
+
if format_options.include?(value)
|
61
|
+
options.output_format = value
|
62
|
+
else
|
63
|
+
abort("format '#{value}' not found")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on('--html-renderer', 'Use the HtmlRenderer renderer rather than the native C renderer (only valid when format is html)') do
|
55
68
|
options.renderer = true
|
56
69
|
end
|
57
70
|
|
@@ -88,11 +101,23 @@ end
|
|
88
101
|
|
89
102
|
options = parse_options
|
90
103
|
|
104
|
+
abort("format '#{options.output_format}' does not support using the HtmlRenderer renderer") if
|
105
|
+
options.renderer && options.output_format != :html
|
106
|
+
|
91
107
|
doc = CommonMarker.render_doc(ARGF.read, options.active_parse_options, options.active_extensions)
|
92
108
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
109
|
+
case options.output_format
|
110
|
+
when :html
|
111
|
+
if options.renderer
|
112
|
+
renderer = CommonMarker::HtmlRenderer.new(options: options.active_render_options, extensions: options.active_extensions)
|
113
|
+
$stdout.write(renderer.render(doc))
|
114
|
+
else
|
115
|
+
$stdout.write(doc.to_html(options.active_render_options, options.active_extensions))
|
116
|
+
end
|
117
|
+
when :xml
|
118
|
+
$stdout.write(doc.to_xml(options.active_render_options))
|
119
|
+
when :commonmark
|
120
|
+
$stdout.write(doc.to_commonmark(options.active_render_options))
|
121
|
+
when :plaintext
|
122
|
+
$stdout.write(doc.to_plaintext(options.active_render_options))
|
98
123
|
end
|
data/commonmarker.gemspec
CHANGED
@@ -21,13 +21,12 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
s.executables = ['commonmarker']
|
23
23
|
s.require_paths = %w[lib ext]
|
24
|
+
s.required_ruby_version = ['>= 2.6', '< 4.0']
|
24
25
|
|
25
26
|
s.rdoc_options += ['-x', 'ext/commonmarker/cmark/.*']
|
26
27
|
|
27
|
-
s.add_dependency 'ruby-enum', '~> 0.5'
|
28
|
-
|
29
28
|
s.add_development_dependency 'awesome_print'
|
30
|
-
s.add_development_dependency 'json', '~>
|
29
|
+
s.add_development_dependency 'json', '~> 2.3'
|
31
30
|
s.add_development_dependency 'minitest', '~> 5.6'
|
32
31
|
s.add_development_dependency 'minitest-focus', '~> 1.1'
|
33
32
|
s.add_development_dependency 'rake'
|
@@ -45,6 +45,13 @@ static VALUE encode_utf8_string(const char *c_string) {
|
|
45
45
|
return string;
|
46
46
|
}
|
47
47
|
|
48
|
+
/* Encode a C string using the encoding from Ruby string +source+. */
|
49
|
+
static VALUE encode_source_string(const char *c_string, VALUE source) {
|
50
|
+
VALUE string = rb_str_new2(c_string);
|
51
|
+
rb_enc_copy(string, source);
|
52
|
+
return string;
|
53
|
+
}
|
54
|
+
|
48
55
|
static void rb_mark_c_struct(void *data) {
|
49
56
|
cmark_node *node = data;
|
50
57
|
cmark_node *child;
|
@@ -179,6 +186,40 @@ static VALUE rb_markdown_to_html(VALUE self, VALUE rb_text, VALUE rb_options, VA
|
|
179
186
|
return ruby_html;
|
180
187
|
}
|
181
188
|
|
189
|
+
/*
|
190
|
+
* Internal: Parses a Markdown string into an HTML string.
|
191
|
+
*
|
192
|
+
*/
|
193
|
+
static VALUE rb_markdown_to_xml(VALUE self, VALUE rb_text, VALUE rb_options, VALUE rb_extensions) {
|
194
|
+
char *str, *xml;
|
195
|
+
int len;
|
196
|
+
cmark_parser *parser;
|
197
|
+
cmark_node *doc;
|
198
|
+
Check_Type(rb_text, T_STRING);
|
199
|
+
Check_Type(rb_options, T_FIXNUM);
|
200
|
+
|
201
|
+
parser = prepare_parser(rb_options, rb_extensions, cmark_get_arena_mem_allocator());
|
202
|
+
|
203
|
+
str = (char *)RSTRING_PTR(rb_text);
|
204
|
+
len = RSTRING_LEN(rb_text);
|
205
|
+
|
206
|
+
cmark_parser_feed(parser, str, len);
|
207
|
+
doc = cmark_parser_finish(parser);
|
208
|
+
if (doc == NULL) {
|
209
|
+
cmark_arena_reset();
|
210
|
+
rb_raise(rb_eNodeError, "error parsing document");
|
211
|
+
}
|
212
|
+
|
213
|
+
cmark_mem *default_mem = cmark_get_default_mem_allocator();
|
214
|
+
xml = cmark_render_xml_with_mem(doc, FIX2INT(rb_options), default_mem);
|
215
|
+
cmark_arena_reset();
|
216
|
+
|
217
|
+
VALUE ruby_xml = rb_str_new2(xml);
|
218
|
+
default_mem->free(xml);
|
219
|
+
|
220
|
+
return ruby_xml;
|
221
|
+
}
|
222
|
+
|
182
223
|
/*
|
183
224
|
* Internal: Creates a node based on a node type.
|
184
225
|
*
|
@@ -567,6 +608,28 @@ static VALUE rb_render_html(VALUE self, VALUE rb_options, VALUE rb_extensions) {
|
|
567
608
|
return ruby_html;
|
568
609
|
}
|
569
610
|
|
611
|
+
/* Internal: Convert the node to an XML string.
|
612
|
+
*
|
613
|
+
* Returns a {String}.
|
614
|
+
*/
|
615
|
+
static VALUE rb_render_xml(VALUE self, VALUE rb_options) {
|
616
|
+
int options;
|
617
|
+
int i;
|
618
|
+
cmark_node *node;
|
619
|
+
Check_Type(rb_options, T_FIXNUM);
|
620
|
+
|
621
|
+
options = FIX2INT(rb_options);
|
622
|
+
|
623
|
+
Data_Get_Struct(self, cmark_node, node);
|
624
|
+
|
625
|
+
char *xml = cmark_render_xml(node, options);
|
626
|
+
VALUE ruby_xml = rb_str_new2(xml);
|
627
|
+
|
628
|
+
free(xml);
|
629
|
+
|
630
|
+
return ruby_xml;
|
631
|
+
}
|
632
|
+
|
570
633
|
/* Internal: Convert the node to a CommonMark string.
|
571
634
|
*
|
572
635
|
* Returns a {String}.
|
@@ -1130,7 +1193,8 @@ static VALUE rb_html_escape_href(VALUE self, VALUE rb_text) {
|
|
1130
1193
|
if (houdini_escape_href(&buf, (const uint8_t *)RSTRING_PTR(rb_text),
|
1131
1194
|
RSTRING_LEN(rb_text))) {
|
1132
1195
|
result = (char *)cmark_strbuf_detach(&buf);
|
1133
|
-
return
|
1196
|
+
return encode_source_string(result, rb_text);
|
1197
|
+
|
1134
1198
|
}
|
1135
1199
|
|
1136
1200
|
return rb_text;
|
@@ -1150,7 +1214,7 @@ static VALUE rb_html_escape_html(VALUE self, VALUE rb_text) {
|
|
1150
1214
|
if (houdini_escape_html0(&buf, (const uint8_t *)RSTRING_PTR(rb_text),
|
1151
1215
|
RSTRING_LEN(rb_text), 0)) {
|
1152
1216
|
result = (char *)cmark_strbuf_detach(&buf);
|
1153
|
-
return
|
1217
|
+
return encode_source_string(result, rb_text);
|
1154
1218
|
}
|
1155
1219
|
|
1156
1220
|
return rb_text;
|
@@ -1208,6 +1272,8 @@ __attribute__((visibility("default"))) void Init_commonmarker() {
|
|
1208
1272
|
rb_cNode = rb_define_class_under(module, "Node", rb_cObject);
|
1209
1273
|
rb_define_singleton_method(rb_cNode, "markdown_to_html", rb_markdown_to_html,
|
1210
1274
|
3);
|
1275
|
+
rb_define_singleton_method(rb_cNode, "markdown_to_xml", rb_markdown_to_xml,
|
1276
|
+
3);
|
1211
1277
|
rb_define_singleton_method(rb_cNode, "new", rb_node_new, 1);
|
1212
1278
|
rb_define_singleton_method(rb_cNode, "parse_document", rb_parse_document, 4);
|
1213
1279
|
rb_define_method(rb_cNode, "string_content", rb_node_get_string_content, 0);
|
@@ -1220,6 +1286,7 @@ __attribute__((visibility("default"))) void Init_commonmarker() {
|
|
1220
1286
|
rb_define_method(rb_cNode, "next", rb_node_next, 0);
|
1221
1287
|
rb_define_method(rb_cNode, "insert_before", rb_node_insert_before, 1);
|
1222
1288
|
rb_define_method(rb_cNode, "_render_html", rb_render_html, 2);
|
1289
|
+
rb_define_method(rb_cNode, "_render_xml", rb_render_xml, 1);
|
1223
1290
|
rb_define_method(rb_cNode, "_render_commonmark", rb_render_commonmark, -1);
|
1224
1291
|
rb_define_method(rb_cNode, "_render_plaintext", rb_render_plaintext, -1);
|
1225
1292
|
rb_define_method(rb_cNode, "insert_after", rb_node_insert_after, 1);
|
data/lib/commonmarker/config.rb
CHANGED
@@ -1,53 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'ruby-enum'
|
4
3
|
module CommonMarker
|
5
4
|
# For Ruby::Enum, these must be classes, not modules
|
6
5
|
module Config
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
6
|
+
# See https://github.com/github/cmark-gfm/blob/master/src/cmark-gfm.h#L673
|
7
|
+
OPTS = {
|
8
|
+
parse: {
|
9
|
+
DEFAULT: 0,
|
10
|
+
VALIDATE_UTF8: (1 << 9),
|
11
|
+
SMART: (1 << 10),
|
12
|
+
LIBERAL_HTML_TAG: (1 << 12),
|
13
|
+
FOOTNOTES: (1 << 13),
|
14
|
+
STRIKETHROUGH_DOUBLE_TILDE: (1 << 14),
|
15
|
+
UNSAFE: (1 << 17)
|
16
|
+
}.freeze,
|
17
|
+
render: {
|
18
|
+
DEFAULT: 0,
|
19
|
+
SOURCEPOS: (1 << 1),
|
20
|
+
HARDBREAKS: (1 << 2),
|
21
|
+
NOBREAKS: (1 << 4),
|
22
|
+
GITHUB_PRE_LANG: (1 << 11),
|
23
|
+
TABLE_PREFER_STYLE_ATTRIBUTES: (1 << 15),
|
24
|
+
FULL_INFO_STRING: (1 << 16),
|
25
|
+
UNSAFE: (1 << 17),
|
26
|
+
FOOTNOTES: (1 << 13)
|
27
|
+
}.freeze,
|
28
|
+
format: %i[html xml commonmark plaintext].freeze
|
29
|
+
}.freeze
|
31
30
|
|
32
31
|
def self.process_options(option, type)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
case option
|
33
|
+
when Symbol
|
34
|
+
OPTS.fetch(type).fetch(option)
|
35
|
+
when Array
|
36
|
+
raise TypeError if option.none?
|
37
|
+
|
39
38
|
# neckbearding around. the map will both check the opts and then bitwise-OR it
|
40
|
-
option.
|
41
|
-
check_option(o, type)
|
42
|
-
type.to_h[o]
|
43
|
-
end.inject(0, :|)
|
39
|
+
OPTS.fetch(type).fetch_values(*option).inject(0, :|)
|
44
40
|
else
|
45
|
-
raise TypeError, "option type must be a valid symbol or array of symbols within the #{type} context"
|
41
|
+
raise TypeError, "option type must be a valid symbol or array of symbols within the #{name}::OPTS[:#{type}] context"
|
46
42
|
end
|
47
|
-
|
48
|
-
|
49
|
-
def self.check_option(option, type)
|
50
|
-
raise TypeError, "option ':#{option}' does not exist for #{type}" unless type.key?(option)
|
43
|
+
rescue KeyError => e
|
44
|
+
raise TypeError, "option ':#{e.key}' does not exist for #{name}::OPTS[:#{type}]"
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
@@ -11,7 +11,7 @@ module CommonMarker
|
|
11
11
|
PP.pp(self, +'', Float::INFINITY)
|
12
12
|
end
|
13
13
|
|
14
|
-
# @param [PrettyPrint] pp
|
14
|
+
# @param printer [PrettyPrint] pp
|
15
15
|
def pretty_print(printer)
|
16
16
|
printer.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", '>') do
|
17
17
|
printer.breakable
|
@@ -27,11 +27,9 @@ module CommonMarker
|
|
27
27
|
list_tight
|
28
28
|
fence_info
|
29
29
|
].map do |name|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
nil
|
34
|
-
end
|
30
|
+
[name, __send__(name)]
|
31
|
+
rescue NodeError
|
32
|
+
nil
|
35
33
|
end.compact
|
36
34
|
|
37
35
|
printer.seplist(attrs) do |name, value|
|
data/lib/commonmarker/node.rb
CHANGED
@@ -11,7 +11,7 @@ module CommonMarker
|
|
11
11
|
#
|
12
12
|
# blk - A {Proc} representing the action to take for each child
|
13
13
|
def walk(&block)
|
14
|
-
return enum_for(:walk) unless
|
14
|
+
return enum_for(:walk) unless block
|
15
15
|
|
16
16
|
yield self
|
17
17
|
each do |child|
|
@@ -30,6 +30,16 @@ module CommonMarker
|
|
30
30
|
_render_html(opts, extensions).force_encoding('utf-8')
|
31
31
|
end
|
32
32
|
|
33
|
+
# Public: Convert the node to an XML string.
|
34
|
+
#
|
35
|
+
# options - A {Symbol} or {Array of Symbol}s indicating the render options
|
36
|
+
#
|
37
|
+
# Returns a {String}.
|
38
|
+
def to_xml(options = :DEFAULT)
|
39
|
+
opts = Config.process_options(options, :render)
|
40
|
+
_render_xml(opts).force_encoding('utf-8')
|
41
|
+
end
|
42
|
+
|
33
43
|
# Public: Convert the node to a CommonMark string.
|
34
44
|
#
|
35
45
|
# options - A {Symbol} or {Array of Symbol}s indicating the render options
|
@@ -6,6 +6,7 @@ require 'stringio'
|
|
6
6
|
module CommonMarker
|
7
7
|
class Renderer
|
8
8
|
attr_accessor :in_tight, :warnings, :in_plain
|
9
|
+
|
9
10
|
def initialize(options: :DEFAULT, extensions: [])
|
10
11
|
@opts = Config.process_options(options, :render)
|
11
12
|
@stream = StringIO.new(+'')
|
@@ -18,11 +19,12 @@ module CommonMarker
|
|
18
19
|
|
19
20
|
def out(*args)
|
20
21
|
args.each do |arg|
|
21
|
-
|
22
|
+
case arg
|
23
|
+
when :children
|
22
24
|
@node.each { |child| out(child) }
|
23
|
-
|
25
|
+
when Array
|
24
26
|
arg.each { |x| render(x) }
|
25
|
-
|
27
|
+
when Node
|
26
28
|
render(arg)
|
27
29
|
else
|
28
30
|
@stream.write(arg)
|
@@ -127,7 +129,7 @@ module CommonMarker
|
|
127
129
|
end
|
128
130
|
|
129
131
|
def option_enabled?(opt)
|
130
|
-
(@opts & CommonMarker::Config::
|
132
|
+
(@opts & CommonMarker::Config::OPTS.dig(:render, opt)) != 0
|
131
133
|
end
|
132
134
|
end
|
133
135
|
end
|
data/lib/commonmarker/version.rb
CHANGED
data/test/benchmark.rb
CHANGED
@@ -19,10 +19,6 @@ dobench('redcarpet') do
|
|
19
19
|
Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: false, tables: false).render(benchinput)
|
20
20
|
end
|
21
21
|
|
22
|
-
dobench('github-markdown') do
|
23
|
-
GitHub::Markdown.render(benchinput)
|
24
|
-
end
|
25
|
-
|
26
22
|
dobench('commonmarker with to_html') do
|
27
23
|
CommonMarker.render_html(benchinput)
|
28
24
|
end
|
data/test/test_commands.rb
CHANGED
@@ -5,27 +5,68 @@ require 'test_helper'
|
|
5
5
|
class TestCommands < Minitest::Test
|
6
6
|
def test_basic
|
7
7
|
out = make_bin('strong.md')
|
8
|
-
assert_equal
|
8
|
+
assert_equal('<p>I am <strong>strong</strong></p>', out)
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_does_not_have_extensions
|
12
12
|
out = make_bin('table.md')
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
assert_includes out, '| a'
|
14
|
+
refute_includes out, '<p><del>hi</del>'
|
15
|
+
refute_includes out, '<table> <tr> <th> a </th> <td> c </td>'
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_understands_extensions
|
19
19
|
out = make_bin('table.md', '--extension=table')
|
20
|
-
|
21
|
-
|
22
|
-
%w[<table> <tr> <th> a </th> <td> c </td>].each { |html|
|
20
|
+
refute_includes out, '| a'
|
21
|
+
refute_includes out, '<p><del>hi</del>'
|
22
|
+
%w[<table> <tr> <th> a </th> <td> c </td>].each { |html| assert_includes out, html }
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_understands_multiple_extensions
|
26
26
|
out = make_bin('table.md', '--extension=table,strikethrough')
|
27
|
-
|
28
|
-
|
29
|
-
%w[<table> <tr> <th> a </th> <td> c </td>].each { |html|
|
27
|
+
refute_includes out, '| a'
|
28
|
+
assert_includes out, '<p><del>hi</del>'
|
29
|
+
%w[<table> <tr> <th> a </th> <td> c </td>].each { |html| assert_includes out, html }
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_understands_html_format_with_renderer_and_extensions
|
33
|
+
out = make_bin('table.md', '--to=html --extension=table,strikethrough --html-renderer')
|
34
|
+
refute_includes out, '| a'
|
35
|
+
assert_includes out, '<p><del>hi</del>'
|
36
|
+
%w[<table> <tr> <th> a </th> <td> c </td>].each { |html| assert_includes out, html }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_understands_xml_format
|
40
|
+
out = make_bin('strong.md', '--to=xml')
|
41
|
+
assert_includes out, '<?xml version="1.0" encoding="UTF-8"?>'
|
42
|
+
assert_includes out, '<text xml:space="preserve">strong</text>'
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_understands_commonmark_format
|
46
|
+
out = make_bin('strong.md', '--to=commonmark')
|
47
|
+
assert_equal('I am **strong**', out)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_understands_plaintext_format
|
51
|
+
out = make_bin('strong.md', '--to=plaintext')
|
52
|
+
assert_equal('I am strong', out)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_aborts_invalid_format
|
56
|
+
_out, err = capture_subprocess_io do
|
57
|
+
make_bin('strong.md', '--to=unknown')
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_match "format 'unknown' not found", err
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_aborts_format_and_html_renderer_combinations
|
64
|
+
(CommonMarker::Config::OPTS[:format] - [:html]).each do |format|
|
65
|
+
_out, err = capture_subprocess_io do
|
66
|
+
make_bin('strong.md', "--to=#{format} --html-renderer")
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_match "format '#{format}' does not support using the HtmlRenderer renderer", err
|
70
|
+
end
|
30
71
|
end
|
31
72
|
end
|
data/test/test_commonmark.rb
CHANGED
@@ -30,7 +30,7 @@ class TestCommonmark < Minitest::Test
|
|
30
30
|
compare = render_doc(@markdown).to_commonmark
|
31
31
|
|
32
32
|
assert_equal \
|
33
|
-
render_doc(@markdown).to_html.
|
34
|
-
render_doc(compare).to_html.
|
33
|
+
render_doc(@markdown).to_html.squeeze(' ').gsub(HTML_COMMENT, ''),
|
34
|
+
render_doc(compare).to_html.squeeze(' ').gsub(HTML_COMMENT, '')
|
35
35
|
end
|
36
36
|
end
|
data/test/test_doc.rb
CHANGED
@@ -17,114 +17,114 @@ class TestDocNode < Minitest::Test
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_get_type
|
20
|
-
assert_equal @doc.type
|
20
|
+
assert_equal(:document, @doc.type)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_get_type_string
|
24
|
-
assert_equal @doc.type_string
|
24
|
+
assert_equal('document', @doc.type_string)
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_get_first_child
|
28
|
-
assert_equal @first_child.type
|
28
|
+
assert_equal(:paragraph, @first_child.type)
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_get_next
|
32
|
-
assert_equal @first_child.first_child.next.type
|
32
|
+
assert_equal(:emph, @first_child.first_child.next.type)
|
33
33
|
end
|
34
34
|
|
35
35
|
def test_insert_before
|
36
36
|
paragraph = Node.new(:paragraph)
|
37
|
-
|
37
|
+
assert(@first_child.insert_before(paragraph))
|
38
38
|
assert_match "<p></p>\n<p>Hi <em>there</em>.", @doc.to_html
|
39
39
|
end
|
40
40
|
|
41
41
|
def test_insert_after
|
42
42
|
paragraph = Node.new(:paragraph)
|
43
|
-
|
43
|
+
assert(@first_child.insert_after(paragraph))
|
44
44
|
assert_match "<strong>many nodes</strong>!</p>\n<p></p>\n", @doc.to_html
|
45
45
|
end
|
46
46
|
|
47
47
|
def test_prepend_child
|
48
48
|
code = Node.new(:code)
|
49
|
-
|
49
|
+
assert(@first_child.prepend_child(code))
|
50
50
|
assert_match '<p><code></code>Hi <em>there</em>.', @doc.to_html
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_append_child
|
54
54
|
strong = Node.new(:strong)
|
55
|
-
|
55
|
+
assert(@first_child.append_child(strong))
|
56
56
|
assert_match "!<strong></strong></p>\n", @doc.to_html
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_get_last_child
|
60
|
-
assert_equal @last_child.type
|
60
|
+
assert_equal(:paragraph, @last_child.type)
|
61
61
|
end
|
62
62
|
|
63
63
|
def test_get_parent
|
64
|
-
assert_equal @first_child.first_child.next.parent.type
|
64
|
+
assert_equal(:paragraph, @first_child.first_child.next.parent.type)
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_get_previous
|
68
|
-
assert_equal @first_child.first_child.next.previous.type
|
68
|
+
assert_equal(:text, @first_child.first_child.next.previous.type)
|
69
69
|
end
|
70
70
|
|
71
71
|
def test_get_url
|
72
|
-
assert_equal
|
72
|
+
assert_equal('https://www.github.com', @link.url)
|
73
73
|
end
|
74
74
|
|
75
75
|
def test_set_url
|
76
|
-
assert_equal
|
76
|
+
assert_equal('https://www.mozilla.org', @link.url = 'https://www.mozilla.org')
|
77
77
|
end
|
78
78
|
|
79
79
|
def test_get_title
|
80
|
-
assert_equal @image.title
|
80
|
+
assert_equal('Favicon', @image.title)
|
81
81
|
end
|
82
82
|
|
83
83
|
def test_set_title
|
84
|
-
assert_equal @image.title = 'Octocat'
|
84
|
+
assert_equal('Octocat', @image.title = 'Octocat')
|
85
85
|
end
|
86
86
|
|
87
87
|
def test_get_header_level
|
88
|
-
assert_equal @header.header_level
|
88
|
+
assert_equal(3, @header.header_level)
|
89
89
|
end
|
90
90
|
|
91
91
|
def test_set_header_level
|
92
|
-
assert_equal @header.header_level = 6
|
92
|
+
assert_equal(6, @header.header_level = 6)
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_get_list_type
|
96
|
-
assert_equal @ul_list.list_type
|
97
|
-
assert_equal @ol_list.list_type
|
96
|
+
assert_equal(:bullet_list, @ul_list.list_type)
|
97
|
+
assert_equal(:ordered_list, @ol_list.list_type)
|
98
98
|
end
|
99
99
|
|
100
100
|
def test_set_list_type
|
101
|
-
assert_equal @ul_list.list_type = :ordered_list
|
102
|
-
assert_equal @ol_list.list_type = :bullet_list
|
101
|
+
assert_equal(:ordered_list, @ul_list.list_type = :ordered_list)
|
102
|
+
assert_equal(:bullet_list, @ol_list.list_type = :bullet_list)
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_get_list_start
|
106
|
-
assert_equal @ol_list.list_start
|
106
|
+
assert_equal(1, @ol_list.list_start)
|
107
107
|
end
|
108
108
|
|
109
109
|
def test_set_list_start
|
110
|
-
assert_equal @ol_list.list_start = 8
|
110
|
+
assert_equal(8, @ol_list.list_start = 8)
|
111
111
|
end
|
112
112
|
|
113
113
|
def test_get_list_tight
|
114
|
-
|
115
|
-
|
114
|
+
assert(@ul_list.list_tight)
|
115
|
+
assert(@ol_list.list_tight)
|
116
116
|
end
|
117
117
|
|
118
118
|
def test_set_list_tight
|
119
|
-
|
120
|
-
|
119
|
+
refute(@ul_list.list_tight = false)
|
120
|
+
refute(@ol_list.list_tight = false)
|
121
121
|
end
|
122
122
|
|
123
123
|
def test_get_fence_info
|
124
|
-
assert_equal @fence.fence_info
|
124
|
+
assert_equal('ruby', @fence.fence_info)
|
125
125
|
end
|
126
126
|
|
127
127
|
def test_set_fence_info
|
128
|
-
assert_equal @fence.fence_info = 'javascript'
|
128
|
+
assert_equal('javascript', @fence.fence_info = 'javascript')
|
129
129
|
end
|
130
130
|
end
|
data/test/test_encoding.rb
CHANGED
@@ -8,13 +8,16 @@ class TestEncoding < Minitest::Test
|
|
8
8
|
contents = fixtures_file('curly.md')
|
9
9
|
doc = CommonMarker.render_doc(contents, :SMART)
|
10
10
|
render = doc.to_html
|
11
|
-
assert_equal
|
11
|
+
assert_equal('<p>This curly quote “makes commonmarker throw an exception”.</p>', render.rstrip)
|
12
|
+
|
13
|
+
render = doc.to_xml
|
14
|
+
assert_includes(render, '<text xml:space="preserve">This curly quote “makes commonmarker throw an exception”.</text>')
|
12
15
|
end
|
13
16
|
|
14
17
|
def test_string_content_is_utf8
|
15
18
|
doc = CommonMarker.render_doc('Hi *there*')
|
16
19
|
text = doc.first_child.last_child.first_child
|
17
|
-
assert_equal text.string_content
|
18
|
-
assert_equal text.string_content.encoding.name
|
20
|
+
assert_equal('there', text.string_content)
|
21
|
+
assert_equal('UTF-8', text.string_content.encoding.name)
|
19
22
|
end
|
20
23
|
end
|
data/test/test_extensions.rb
CHANGED
@@ -9,30 +9,30 @@ class TestExtensions < Minitest::Test
|
|
9
9
|
|
10
10
|
def test_uses_specified_extensions
|
11
11
|
CommonMarker.render_html(@markdown, :DEFAULT, %i[]).tap do |out|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
assert_includes out, '| a'
|
13
|
+
assert_includes out, '| <strong>x</strong>'
|
14
|
+
assert_includes out, '~~hi~~'
|
15
15
|
end
|
16
16
|
|
17
17
|
CommonMarker.render_html(@markdown, :DEFAULT, %i[table]).tap do |out|
|
18
|
-
|
19
|
-
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html|
|
20
|
-
|
18
|
+
refute_includes out, '| a'
|
19
|
+
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html| assert_includes out, html }
|
20
|
+
assert_includes out, '~~hi~~'
|
21
21
|
end
|
22
22
|
|
23
23
|
CommonMarker.render_html(@markdown, :DEFAULT, %i[strikethrough]).tap do |out|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
assert_includes out, '| a'
|
25
|
+
refute_includes out, '~~hi~~'
|
26
|
+
assert_includes out, '<del>hi</del>'
|
27
27
|
end
|
28
28
|
|
29
29
|
doc = CommonMarker.render_doc('~a~ ~~b~~ ~~~c~~~', :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough])
|
30
|
-
assert_equal
|
30
|
+
assert_equal("<p>~a~ <del>b</del> ~~~c~~~</p>\n", doc.to_html)
|
31
31
|
|
32
32
|
CommonMarker.render_html(@markdown, :DEFAULT, %i[table strikethrough]).tap do |out|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
refute_includes out, '| a'
|
34
|
+
refute_includes out, '| <strong>x</strong>'
|
35
|
+
refute_includes out, '~~hi~~'
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -40,19 +40,19 @@ class TestExtensions < Minitest::Test
|
|
40
40
|
doc = CommonMarker.render_doc(@markdown, :DEFAULT, %i[table])
|
41
41
|
|
42
42
|
doc.to_html.tap do |out|
|
43
|
-
|
44
|
-
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html|
|
45
|
-
|
43
|
+
refute_includes out, '| a'
|
44
|
+
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html| assert_includes out, html }
|
45
|
+
assert_includes out, '~~hi~~'
|
46
46
|
end
|
47
47
|
|
48
48
|
HtmlRenderer.new.render(doc).tap do |out|
|
49
|
-
|
50
|
-
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html|
|
51
|
-
|
49
|
+
refute_includes out, '| a'
|
50
|
+
%w[<table> <tr> <th> a </th> <td> c </td> <strong>x</strong>].each { |html| assert_includes out, html }
|
51
|
+
assert_includes out, '~~hi~~'
|
52
52
|
end
|
53
53
|
|
54
54
|
doc = CommonMarker.render_doc('~a~ ~~b~~ ~~~c~~~', :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough])
|
55
|
-
assert_equal
|
55
|
+
assert_equal("<p>~a~ <del>b</del> ~~~c~~~</p>\n", HtmlRenderer.new.render(doc))
|
56
56
|
end
|
57
57
|
|
58
58
|
def test_bad_extension_specifications
|
data/test/test_footnotes.rb
CHANGED
@@ -24,4 +24,25 @@ class TestFootnotes < Minitest::Test
|
|
24
24
|
def test_html_renderer
|
25
25
|
assert_equal @expected, CommonMarker::HtmlRenderer.new.render(@doc)
|
26
26
|
end
|
27
|
+
|
28
|
+
def test_render_html
|
29
|
+
md = <<~MARKDOWN
|
30
|
+
# footnotes
|
31
|
+
Let's render some footnotes[^1]
|
32
|
+
|
33
|
+
[^1]: This is a footnote
|
34
|
+
MARKDOWN
|
35
|
+
expected = <<~HTML
|
36
|
+
<h1>footnotes</h1>
|
37
|
+
<p>Let's render some footnotes<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p>
|
38
|
+
<section class="footnotes">
|
39
|
+
<ol>
|
40
|
+
<li id="fn1">
|
41
|
+
<p>This is a footnote <a href="#fnref1" class="footnote-backref">↩</a></p>
|
42
|
+
</li>
|
43
|
+
</ol>
|
44
|
+
</section>
|
45
|
+
HTML
|
46
|
+
assert_equal expected, CommonMarker.render_html(md, :FOOTNOTES)
|
47
|
+
end
|
27
48
|
end
|
data/test/test_helper.rb
CHANGED
@@ -44,8 +44,8 @@ def open_spec_file(filename)
|
|
44
44
|
example_number += 1
|
45
45
|
end_line = line_number
|
46
46
|
tests << {
|
47
|
-
markdown: markdown_lines.join
|
48
|
-
html: html_lines.join
|
47
|
+
markdown: markdown_lines.join.tr('→', "\t"),
|
48
|
+
html: html_lines.join.tr('→', "\t").rstrip,
|
49
49
|
example: example_number,
|
50
50
|
start_line: start_line,
|
51
51
|
end_line: end_line,
|
data/test/test_maliciousness.rb
CHANGED
@@ -70,7 +70,7 @@ module CommonMarker
|
|
70
70
|
err = assert_raises TypeError do
|
71
71
|
CommonMarker.render_html("foo \n baz", [:SMART])
|
72
72
|
end
|
73
|
-
assert_equal
|
73
|
+
assert_equal('option \':SMART\' does not exist for CommonMarker::Config::OPTS[:render]', err.message)
|
74
74
|
|
75
75
|
assert_raises TypeError do
|
76
76
|
CommonMarker.render_doc("foo \n baz", 123)
|
@@ -79,7 +79,7 @@ module CommonMarker
|
|
79
79
|
err = assert_raises TypeError do
|
80
80
|
CommonMarker.render_doc("foo \n baz", :safe)
|
81
81
|
end
|
82
|
-
assert_equal
|
82
|
+
assert_equal('option \':safe\' does not exist for CommonMarker::Config::OPTS[:parse]', err.message)
|
83
83
|
|
84
84
|
assert_raises TypeError do
|
85
85
|
CommonMarker.render_doc("foo \n baz", :totes_fake)
|
@@ -10,7 +10,7 @@ end
|
|
10
10
|
# list of pairs consisting of input and a regex that must match the output.
|
11
11
|
pathological = {
|
12
12
|
'nested strong emph' =>
|
13
|
-
[
|
13
|
+
["#{'*a **a ' * 65_000}b#{' a** a*' * 65_000}",
|
14
14
|
Regexp.compile('(<em>a <strong>a ){65_000}b( a</strong> a</em>){65_000}')],
|
15
15
|
'many emph closers with no openers' =>
|
16
16
|
[('a_ ' * 65_000),
|
@@ -34,10 +34,10 @@ pathological = {
|
|
34
34
|
['**x [a*b**c*](d)',
|
35
35
|
Regexp.compile('\\*\\*x <a href=\'d\'>a<em>b</em><em>c</em></a>')],
|
36
36
|
'nested brackets' =>
|
37
|
-
[
|
37
|
+
["#{'[' * 50_000}a#{']' * 50_000}",
|
38
38
|
Regexp.compile('\[{50000}a\]{50000}')],
|
39
39
|
'nested block quotes' =>
|
40
|
-
[
|
40
|
+
["#{'> ' * 50_000}a",
|
41
41
|
Regexp.compile('(<blockquote>\n){50000}')],
|
42
42
|
'U+0000 in input' =>
|
43
43
|
['abc\u0000de\u0000',
|
@@ -53,41 +53,41 @@ end
|
|
53
53
|
|
54
54
|
if ENV['BENCH']
|
55
55
|
class PathologicalInputsPerformanceTest < Minitest::Benchmark
|
56
|
-
def
|
56
|
+
def test_bench_pathological_one
|
57
57
|
assert_performance_linear 0.99 do |n|
|
58
58
|
star = '*' * (n * 10)
|
59
59
|
markdown("#{star}#{star}hi#{star}#{star}")
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
63
|
+
def test_bench_pathological_two
|
64
64
|
assert_performance_linear 0.99 do |n|
|
65
65
|
c = '`t`t`t`t`t`t' * (n * 10)
|
66
66
|
markdown(c)
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
def
|
70
|
+
def test_bench_pathological_three
|
71
71
|
assert_performance_linear 0.99 do |n|
|
72
72
|
markdown(" [a]: #{'A' * n}\n\n#{'[a][]' * n}\n")
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
76
|
+
def test_bench_pathological_four
|
77
77
|
assert_performance_linear 0.5 do |n|
|
78
78
|
markdown("#{'[' * n}a#{']' * n}")
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
def
|
82
|
+
def test_bench_pathological_five
|
83
83
|
assert_performance_linear 0.99 do |n|
|
84
84
|
markdown("#{'**a *a ' * n}#{'a* a**' * n}")
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
def
|
88
|
+
def test_bench_unbound_recursion
|
89
89
|
assert_performance_linear 0.99 do |n|
|
90
|
-
markdown(
|
90
|
+
markdown("#{'[' * n}foo#{'](bar)' * n}")
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
data/test/test_renderer.rb
CHANGED
@@ -27,4 +27,21 @@ class TestRenderer < Minitest::Test
|
|
27
27
|
results = CommonMarker::HtmlRenderer.new.render(doc)
|
28
28
|
assert_equal 2, results.scan(/<tbody>/).size
|
29
29
|
end
|
30
|
+
|
31
|
+
def test_escape_html_encoding
|
32
|
+
my_renderer = Class.new(HtmlRenderer) do
|
33
|
+
attr_reader :input_encoding, :output_encoding
|
34
|
+
|
35
|
+
def text(node)
|
36
|
+
@input_encoding = node.string_content.encoding
|
37
|
+
escape_html(node.string_content).tap do |escaped|
|
38
|
+
@output_encoding = escaped.encoding
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
renderer = my_renderer.new
|
44
|
+
assert_equal Encoding::UTF_8, renderer.render(@doc).encoding
|
45
|
+
assert_equal renderer.input_encoding, renderer.output_encoding
|
46
|
+
end
|
30
47
|
end
|
data/test/test_xml.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestXml < Minitest::Test
|
6
|
+
def setup
|
7
|
+
@markdown = <<~MD
|
8
|
+
Hi *there*!
|
9
|
+
|
10
|
+
1. I am a numeric list.
|
11
|
+
2. I continue the list.
|
12
|
+
* Suddenly, an unordered list!
|
13
|
+
* What fun!
|
14
|
+
|
15
|
+
Okay, _enough_.
|
16
|
+
|
17
|
+
| a | b |
|
18
|
+
| --- | --- |
|
19
|
+
| c | d |
|
20
|
+
MD
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_doc(doc)
|
24
|
+
CommonMarker.render_doc(doc, :DEFAULT, [:table])
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_to_xml
|
28
|
+
compare = render_doc(@markdown).to_xml(:SOURCEPOS)
|
29
|
+
|
30
|
+
assert_equal <<~XML, compare
|
31
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
32
|
+
<!DOCTYPE document SYSTEM "CommonMark.dtd">
|
33
|
+
<document sourcepos="1:1-12:13" xmlns="http://commonmark.org/xml/1.0">
|
34
|
+
<paragraph sourcepos="1:1-1:11">
|
35
|
+
<text sourcepos="1:1-1:3" xml:space="preserve">Hi </text>
|
36
|
+
<emph sourcepos="1:4-1:10">
|
37
|
+
<text sourcepos="1:5-1:9" xml:space="preserve">there</text>
|
38
|
+
</emph>
|
39
|
+
<text sourcepos="1:11-1:11" xml:space="preserve">!</text>
|
40
|
+
</paragraph>
|
41
|
+
<list sourcepos="3:1-4:23" type="ordered" start="1" delim="period" tight="true">
|
42
|
+
<item sourcepos="3:1-3:23">
|
43
|
+
<paragraph sourcepos="3:4-3:23">
|
44
|
+
<text sourcepos="3:4-3:23" xml:space="preserve">I am a numeric list.</text>
|
45
|
+
</paragraph>
|
46
|
+
</item>
|
47
|
+
<item sourcepos="4:1-4:23">
|
48
|
+
<paragraph sourcepos="4:4-4:23">
|
49
|
+
<text sourcepos="4:4-4:23" xml:space="preserve">I continue the list.</text>
|
50
|
+
</paragraph>
|
51
|
+
</item>
|
52
|
+
</list>
|
53
|
+
<list sourcepos="5:1-7:0" type="bullet" tight="true">
|
54
|
+
<item sourcepos="5:1-5:30">
|
55
|
+
<paragraph sourcepos="5:3-5:30">
|
56
|
+
<text sourcepos="5:3-5:30" xml:space="preserve">Suddenly, an unordered list!</text>
|
57
|
+
</paragraph>
|
58
|
+
</item>
|
59
|
+
<item sourcepos="6:1-7:0">
|
60
|
+
<paragraph sourcepos="6:3-6:11">
|
61
|
+
<text sourcepos="6:3-6:11" xml:space="preserve">What fun!</text>
|
62
|
+
</paragraph>
|
63
|
+
</item>
|
64
|
+
</list>
|
65
|
+
<paragraph sourcepos="8:1-8:15">
|
66
|
+
<text sourcepos="8:1-8:6" xml:space="preserve">Okay, </text>
|
67
|
+
<emph sourcepos="8:7-8:14">
|
68
|
+
<text sourcepos="8:8-8:13" xml:space="preserve">enough</text>
|
69
|
+
</emph>
|
70
|
+
<text sourcepos="8:15-8:15" xml:space="preserve">.</text>
|
71
|
+
</paragraph>
|
72
|
+
<table sourcepos="10:1-12:13">
|
73
|
+
<table_header sourcepos="10:1-10:13">
|
74
|
+
<table_cell sourcepos="10:2-10:6">
|
75
|
+
<text sourcepos="10:3-10:3" xml:space="preserve">a</text>
|
76
|
+
</table_cell>
|
77
|
+
<table_cell sourcepos="10:8-10:12">
|
78
|
+
<text sourcepos="10:9-10:9" xml:space="preserve">b</text>
|
79
|
+
</table_cell>
|
80
|
+
</table_header>
|
81
|
+
<table_row sourcepos="12:1-12:13">
|
82
|
+
<table_cell sourcepos="12:2-12:6">
|
83
|
+
<text sourcepos="12:3-12:3" xml:space="preserve">c</text>
|
84
|
+
</table_cell>
|
85
|
+
<table_cell sourcepos="12:8-12:12">
|
86
|
+
<text sourcepos="12:9-12:9" xml:space="preserve">d</text>
|
87
|
+
</table_cell>
|
88
|
+
</table_row>
|
89
|
+
</table>
|
90
|
+
</document>
|
91
|
+
XML
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_to_xml_with_quotes
|
95
|
+
compare = render_doc('"quotes" should be escaped').to_xml(:DEFAULT)
|
96
|
+
|
97
|
+
assert_equal <<~XML, compare
|
98
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
99
|
+
<!DOCTYPE document SYSTEM "CommonMark.dtd">
|
100
|
+
<document xmlns="http://commonmark.org/xml/1.0">
|
101
|
+
<paragraph>
|
102
|
+
<text xml:space="preserve">"quotes" should be escaped</text>
|
103
|
+
</paragraph>
|
104
|
+
</document>
|
105
|
+
XML
|
106
|
+
end
|
107
|
+
end
|
metadata
CHANGED
@@ -1,30 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: commonmarker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.23.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen Torikian
|
8
8
|
- Ashe Connor
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-08-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: ruby-enum
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - "~>"
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '0.5'
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - "~>"
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '0.5'
|
28
14
|
- !ruby/object:Gem::Dependency
|
29
15
|
name: awesome_print
|
30
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,14 +31,14 @@ dependencies:
|
|
45
31
|
requirements:
|
46
32
|
- - "~>"
|
47
33
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
34
|
+
version: '2.3'
|
49
35
|
type: :development
|
50
36
|
prerelease: false
|
51
37
|
version_requirements: !ruby/object:Gem::Requirement
|
52
38
|
requirements:
|
53
39
|
- - "~>"
|
54
40
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
41
|
+
version: '2.3'
|
56
42
|
- !ruby/object:Gem::Dependency
|
57
43
|
name: minitest
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,7 +139,7 @@ dependencies:
|
|
153
139
|
version: '0'
|
154
140
|
description: A fast, safe, extensible parser for CommonMark. This wraps the official
|
155
141
|
libcmark library.
|
156
|
-
email:
|
142
|
+
email:
|
157
143
|
executables:
|
158
144
|
- commonmarker
|
159
145
|
extensions:
|
@@ -268,11 +254,12 @@ files:
|
|
268
254
|
- test/test_smartpunct.rb
|
269
255
|
- test/test_spec.rb
|
270
256
|
- test/test_tasklists.rb
|
257
|
+
- test/test_xml.rb
|
271
258
|
homepage: https://github.com/gjtorikian/commonmarker
|
272
259
|
licenses:
|
273
260
|
- MIT
|
274
261
|
metadata: {}
|
275
|
-
post_install_message:
|
262
|
+
post_install_message:
|
276
263
|
rdoc_options:
|
277
264
|
- "-x"
|
278
265
|
- ext/commonmarker/cmark/.*
|
@@ -283,15 +270,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
283
270
|
requirements:
|
284
271
|
- - ">="
|
285
272
|
- !ruby/object:Gem::Version
|
286
|
-
version: '
|
273
|
+
version: '2.6'
|
274
|
+
- - "<"
|
275
|
+
- !ruby/object:Gem::Version
|
276
|
+
version: '4.0'
|
287
277
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
288
278
|
requirements:
|
289
279
|
- - ">="
|
290
280
|
- !ruby/object:Gem::Version
|
291
281
|
version: '0'
|
292
282
|
requirements: []
|
293
|
-
rubygems_version: 3.
|
294
|
-
signing_key:
|
283
|
+
rubygems_version: 3.1.4
|
284
|
+
signing_key:
|
295
285
|
specification_version: 4
|
296
286
|
summary: CommonMark parser and renderer. Written in C, wrapped in Ruby.
|
297
287
|
test_files:
|
@@ -317,6 +307,7 @@ test_files:
|
|
317
307
|
- test/test_helper.rb
|
318
308
|
- test/test_options.rb
|
319
309
|
- test/benchmark.rb
|
310
|
+
- test/test_xml.rb
|
320
311
|
- test/test_basics.rb
|
321
312
|
- test/test_renderer.rb
|
322
313
|
- test/test_gc.rb
|