motion-markdown-it 0.4.0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +243 -0
- data/lib/motion-markdown-it.rb +71 -0
- data/lib/motion-markdown-it/common/entities.rb +1084 -0
- data/lib/motion-markdown-it/common/html_blocks.rb +60 -0
- data/lib/motion-markdown-it/common/html_re.rb +28 -0
- data/lib/motion-markdown-it/common/string.rb +14 -0
- data/lib/motion-markdown-it/common/url_schemas.rb +173 -0
- data/lib/motion-markdown-it/common/utils.rb +216 -0
- data/lib/motion-markdown-it/helpers/parse_link_destination.rb +75 -0
- data/lib/motion-markdown-it/helpers/parse_link_label.rb +51 -0
- data/lib/motion-markdown-it/helpers/parse_link_title.rb +48 -0
- data/lib/motion-markdown-it/index.rb +507 -0
- data/lib/motion-markdown-it/parser_block.rb +113 -0
- data/lib/motion-markdown-it/parser_core.rb +46 -0
- data/lib/motion-markdown-it/parser_inline.rb +121 -0
- data/lib/motion-markdown-it/presets/commonmark.rb +76 -0
- data/lib/motion-markdown-it/presets/default.rb +42 -0
- data/lib/motion-markdown-it/presets/zero.rb +59 -0
- data/lib/motion-markdown-it/renderer.rb +286 -0
- data/lib/motion-markdown-it/ruler.rb +327 -0
- data/lib/motion-markdown-it/rules_block/blockquote.rb +138 -0
- data/lib/motion-markdown-it/rules_block/code.rb +35 -0
- data/lib/motion-markdown-it/rules_block/fence.rb +94 -0
- data/lib/motion-markdown-it/rules_block/heading.rb +56 -0
- data/lib/motion-markdown-it/rules_block/hr.rb +45 -0
- data/lib/motion-markdown-it/rules_block/html_block.rb +73 -0
- data/lib/motion-markdown-it/rules_block/lheading.rb +54 -0
- data/lib/motion-markdown-it/rules_block/list.rb +242 -0
- data/lib/motion-markdown-it/rules_block/paragraph.rb +51 -0
- data/lib/motion-markdown-it/rules_block/reference.rb +161 -0
- data/lib/motion-markdown-it/rules_block/state_block.rb +184 -0
- data/lib/motion-markdown-it/rules_block/table.rb +161 -0
- data/lib/motion-markdown-it/rules_core/block.rb +20 -0
- data/lib/motion-markdown-it/rules_core/inline.rb +20 -0
- data/lib/motion-markdown-it/rules_core/linkify.rb +138 -0
- data/lib/motion-markdown-it/rules_core/normalize.rb +44 -0
- data/lib/motion-markdown-it/rules_core/replacements.rb +90 -0
- data/lib/motion-markdown-it/rules_core/smartquotes.rb +158 -0
- data/lib/motion-markdown-it/rules_core/state_core.rb +20 -0
- data/lib/motion-markdown-it/rules_inline/autolink.rb +74 -0
- data/lib/motion-markdown-it/rules_inline/backticks.rb +51 -0
- data/lib/motion-markdown-it/rules_inline/emphasis.rb +172 -0
- data/lib/motion-markdown-it/rules_inline/entity.rb +51 -0
- data/lib/motion-markdown-it/rules_inline/escape.rb +55 -0
- data/lib/motion-markdown-it/rules_inline/html_inline.rb +49 -0
- data/lib/motion-markdown-it/rules_inline/image.rb +158 -0
- data/lib/motion-markdown-it/rules_inline/link.rb +153 -0
- data/lib/motion-markdown-it/rules_inline/newline.rb +47 -0
- data/lib/motion-markdown-it/rules_inline/state_inline.rb +57 -0
- data/lib/motion-markdown-it/rules_inline/strikethrough.rb +130 -0
- data/lib/motion-markdown-it/rules_inline/text.rb +94 -0
- data/lib/motion-markdown-it/token.rb +134 -0
- data/lib/motion-markdown-it/version.rb +5 -0
- data/spec/motion-markdown-it/bench_mark_spec.rb +44 -0
- data/spec/motion-markdown-it/commonmark_spec.rb +16 -0
- data/spec/motion-markdown-it/markdown_it_spec.rb +18 -0
- data/spec/motion-markdown-it/misc_spec.rb +277 -0
- data/spec/motion-markdown-it/ruler_spec.rb +153 -0
- data/spec/motion-markdown-it/testgen_helper.rb +68 -0
- data/spec/motion-markdown-it/token_spec.rb +17 -0
- data/spec/motion-markdown-it/utils_spec.rb +82 -0
- data/spec/spec_helper.rb +6 -0
- metadata +158 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# Process html entity - {, ¯, ", ...
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Entity
|
6
|
+
extend Common::Utils
|
7
|
+
|
8
|
+
DIGITAL_RE = /^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i
|
9
|
+
NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i
|
10
|
+
|
11
|
+
|
12
|
+
#------------------------------------------------------------------------------
|
13
|
+
def self.entity(state, silent)
|
14
|
+
pos = state.pos
|
15
|
+
max = state.posMax
|
16
|
+
|
17
|
+
return false if state.src.charCodeAt(pos) != 0x26 # &
|
18
|
+
|
19
|
+
if pos + 1 < max
|
20
|
+
ch = state.src.charCodeAt(pos + 1)
|
21
|
+
|
22
|
+
if ch == 0x23 # '#'
|
23
|
+
match = state.src.slice_to_end(pos).match(DIGITAL_RE)
|
24
|
+
if match
|
25
|
+
if !silent
|
26
|
+
code = match[1][0].downcase == 'x' ? match[1].slice_to_end(1).to_i(16) : match[1].to_i
|
27
|
+
state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD)
|
28
|
+
end
|
29
|
+
state.pos += match[0].length
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
else
|
33
|
+
match = state.src.slice_to_end(pos).match(NAMED_RE)
|
34
|
+
if match
|
35
|
+
if HTMLEntities::MAPPINGS[match[1]]
|
36
|
+
state.pending += HTMLEntities::MAPPINGS[match[1]].chr(Encoding::UTF_8) if !silent
|
37
|
+
state.pos += match[0].length
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
state.pending += '&' if !silent
|
45
|
+
state.pos += 1
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Proceess escaped chars and hardbreaks
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Escape
|
6
|
+
|
7
|
+
ESCAPED = []
|
8
|
+
|
9
|
+
0.upto(255) { |i| ESCAPED.push(0) }
|
10
|
+
|
11
|
+
'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'.split('').each { |ch| ESCAPED[ch.ord] = 1 }
|
12
|
+
|
13
|
+
|
14
|
+
#------------------------------------------------------------------------------
|
15
|
+
def self.escape(state, silent)
|
16
|
+
pos = state.pos
|
17
|
+
max = state.posMax
|
18
|
+
|
19
|
+
return false if state.src.charCodeAt(pos) != 0x5C # \
|
20
|
+
|
21
|
+
pos += 1
|
22
|
+
|
23
|
+
if pos < max
|
24
|
+
ch = state.src.charCodeAt(pos)
|
25
|
+
|
26
|
+
if ch < 256 && ESCAPED[ch] != 0
|
27
|
+
state.pending += state.src[pos] if !silent
|
28
|
+
state.pos += 2
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
|
32
|
+
if ch == 0x0A
|
33
|
+
if !silent
|
34
|
+
state.push('hardbreak', 'br', 0)
|
35
|
+
end
|
36
|
+
|
37
|
+
pos += 1
|
38
|
+
# skip leading whitespaces from next line
|
39
|
+
while (pos < max && state.src.charCodeAt(pos) == 0x20)
|
40
|
+
pos += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
state.pos = pos
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
state.pending += '\\' if !silent
|
49
|
+
state.pos += 1
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Process html tags
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
|
5
|
+
module RulesInline
|
6
|
+
class HtmlInline
|
7
|
+
include MarkdownIt::Common::HtmlRe
|
8
|
+
|
9
|
+
#------------------------------------------------------------------------------
|
10
|
+
def self.isLetter(ch)
|
11
|
+
lc = ch | 0x20 # to lower case
|
12
|
+
return (lc >= 0x61) && (lc <= 0x7a) # >= a && <= z
|
13
|
+
end
|
14
|
+
|
15
|
+
#------------------------------------------------------------------------------
|
16
|
+
def self.html_inline(state, silent)
|
17
|
+
pos = state.pos
|
18
|
+
|
19
|
+
return false if !state.md.options[:html]
|
20
|
+
|
21
|
+
# Check start
|
22
|
+
max = state.posMax
|
23
|
+
if (state.src.charCodeAt(pos) != 0x3C || pos + 2 >= max) # <
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
# Quick fail on second char
|
28
|
+
ch = state.src.charCodeAt(pos + 1)
|
29
|
+
if (ch != 0x21 && # !
|
30
|
+
ch != 0x3F && # ?
|
31
|
+
ch != 0x2F && # /
|
32
|
+
!isLetter(ch))
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
match = state.src.slice_to_end(pos).match(HTML_TAG_RE)
|
37
|
+
return false if !match
|
38
|
+
|
39
|
+
if !silent
|
40
|
+
token = state.push('html_inline', '', 0)
|
41
|
+
token.content = state.src.slice(pos...(pos + match[0].length))
|
42
|
+
end
|
43
|
+
state.pos += match[0].length
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# Process ![image](<src> "title")
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Image
|
6
|
+
extend Helpers::ParseLinkDestination
|
7
|
+
extend Helpers::ParseLinkLabel
|
8
|
+
extend Helpers::ParseLinkTitle
|
9
|
+
extend Common::Utils
|
10
|
+
|
11
|
+
#------------------------------------------------------------------------------
|
12
|
+
def self.image(state, silent)
|
13
|
+
href = ''
|
14
|
+
oldPos = state.pos
|
15
|
+
max = state.posMax
|
16
|
+
|
17
|
+
return false if (state.src.charCodeAt(state.pos) != 0x21) # !
|
18
|
+
return false if (state.src.charCodeAt(state.pos + 1) != 0x5B) # [
|
19
|
+
|
20
|
+
labelStart = state.pos + 2
|
21
|
+
labelEnd = parseLinkLabel(state, state.pos + 1, false)
|
22
|
+
|
23
|
+
# parser failed to find ']', so it's not a valid link
|
24
|
+
return false if (labelEnd < 0)
|
25
|
+
|
26
|
+
pos = labelEnd + 1
|
27
|
+
if (pos < max && state.src.charCodeAt(pos) == 0x28) # (
|
28
|
+
#
|
29
|
+
# Inline link
|
30
|
+
#
|
31
|
+
|
32
|
+
# [link]( <href> "title" )
|
33
|
+
# ^^ skipping these spaces
|
34
|
+
pos += 1
|
35
|
+
while pos < max
|
36
|
+
code = state.src.charCodeAt(pos)
|
37
|
+
break if (code != 0x20 && code != 0x0A)
|
38
|
+
pos += 1
|
39
|
+
end
|
40
|
+
return false if (pos >= max)
|
41
|
+
|
42
|
+
# [link]( <href> "title" )
|
43
|
+
# ^^^^^^ parsing link destination
|
44
|
+
start = pos
|
45
|
+
res = parseLinkDestination(state.src, pos, state.posMax)
|
46
|
+
if (res[:ok])
|
47
|
+
href = state.md.normalizeLink.call(res[:str])
|
48
|
+
if (state.md.validateLink.call(href))
|
49
|
+
pos = res[:pos]
|
50
|
+
else
|
51
|
+
href = ''
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# [link]( <href> "title" )
|
56
|
+
# ^^ skipping these spaces
|
57
|
+
start = pos
|
58
|
+
while pos < max
|
59
|
+
code = state.src.charCodeAt(pos)
|
60
|
+
break if (code != 0x20 && code != 0x0A)
|
61
|
+
pos += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# [link]( <href> "title" )
|
65
|
+
# ^^^^^^^ parsing link title
|
66
|
+
res = parseLinkTitle(state.src, pos, state.posMax)
|
67
|
+
if (pos < max && start != pos && res[:ok])
|
68
|
+
title = res[:str]
|
69
|
+
pos = res[:pos]
|
70
|
+
|
71
|
+
# [link]( <href> "title" )
|
72
|
+
# ^^ skipping these spaces
|
73
|
+
while pos < max
|
74
|
+
code = state.src.charCodeAt(pos);
|
75
|
+
break if (code != 0x20 && code != 0x0A)
|
76
|
+
pos += 1
|
77
|
+
end
|
78
|
+
else
|
79
|
+
title = ''
|
80
|
+
end
|
81
|
+
|
82
|
+
if (pos >= max || state.src.charCodeAt(pos) != 0x29) # )
|
83
|
+
state.pos = oldPos
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
pos += 1
|
87
|
+
else
|
88
|
+
#
|
89
|
+
# Link reference
|
90
|
+
#
|
91
|
+
return false if state.env[:references].nil?
|
92
|
+
|
93
|
+
# [foo] [bar]
|
94
|
+
# ^^ optional whitespace (can include newlines)
|
95
|
+
while pos < max
|
96
|
+
code = state.src.charCodeAt(pos)
|
97
|
+
break if (code != 0x20 && code != 0x0A)
|
98
|
+
pos += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
if (pos < max && state.src.charCodeAt(pos) == 0x5B) # [
|
102
|
+
start = pos + 1
|
103
|
+
pos = parseLinkLabel(state, pos)
|
104
|
+
if (pos >= 0)
|
105
|
+
label = state.src.slice(start...pos)
|
106
|
+
pos += 1
|
107
|
+
else
|
108
|
+
pos = labelEnd + 1
|
109
|
+
end
|
110
|
+
else
|
111
|
+
pos = labelEnd + 1
|
112
|
+
end
|
113
|
+
|
114
|
+
# covers label === '' and label === undefined
|
115
|
+
# (collapsed reference link and shortcut reference link respectively)
|
116
|
+
label = state.src.slice(labelStart...labelEnd) if label.nil? || label.empty?
|
117
|
+
|
118
|
+
ref = state.env[:references][normalizeReference(label)]
|
119
|
+
if (!ref)
|
120
|
+
state.pos = oldPos
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
href = ref[:href]
|
124
|
+
title = ref[:title]
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# We found the end of the link, and know for a fact it's a valid link;
|
129
|
+
# so all that's left to do is to call tokenizer.
|
130
|
+
#
|
131
|
+
if (!silent)
|
132
|
+
state.pos = labelStart
|
133
|
+
state.posMax = labelEnd
|
134
|
+
|
135
|
+
newState = RulesInline::StateInline.new(
|
136
|
+
state.src.slice(labelStart...labelEnd),
|
137
|
+
state.md,
|
138
|
+
state.env,
|
139
|
+
tokens = []
|
140
|
+
)
|
141
|
+
newState.md.inline.tokenize(newState)
|
142
|
+
|
143
|
+
token = state.push('image', 'img', 0)
|
144
|
+
token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]
|
145
|
+
token.children = tokens
|
146
|
+
unless (title.nil? || title.empty?)
|
147
|
+
attrs.push([ 'title', title ])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
state.pos = pos
|
152
|
+
state.posMax = max
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Process [link](<to> "stuff")
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Link
|
6
|
+
extend Helpers::ParseLinkDestination
|
7
|
+
extend Helpers::ParseLinkLabel
|
8
|
+
extend Helpers::ParseLinkTitle
|
9
|
+
extend Common::Utils
|
10
|
+
|
11
|
+
#------------------------------------------------------------------------------
|
12
|
+
def self.link(state, silent)
|
13
|
+
href = ''
|
14
|
+
oldPos = state.pos
|
15
|
+
max = state.posMax
|
16
|
+
start = state.pos
|
17
|
+
|
18
|
+
return false if (state.src.charCodeAt(state.pos) != 0x5B) # [
|
19
|
+
|
20
|
+
labelStart = state.pos + 1
|
21
|
+
labelEnd = parseLinkLabel(state, state.pos, true)
|
22
|
+
|
23
|
+
# parser failed to find ']', so it's not a valid link
|
24
|
+
return false if (labelEnd < 0)
|
25
|
+
|
26
|
+
pos = labelEnd + 1
|
27
|
+
if (pos < max && state.src.charCodeAt(pos) == 0x28) # (
|
28
|
+
#
|
29
|
+
# Inline link
|
30
|
+
#
|
31
|
+
|
32
|
+
# [link]( <href> "title" )
|
33
|
+
# ^^ skipping these spaces
|
34
|
+
pos += 1
|
35
|
+
while pos < max
|
36
|
+
code = state.src.charCodeAt(pos)
|
37
|
+
break if (code != 0x20 && code != 0x0A)
|
38
|
+
pos += 1
|
39
|
+
end
|
40
|
+
return false if (pos >= max)
|
41
|
+
|
42
|
+
# [link]( <href> "title" )
|
43
|
+
# ^^^^^^ parsing link destination
|
44
|
+
start = pos
|
45
|
+
res = parseLinkDestination(state.src, pos, state.posMax)
|
46
|
+
if (res[:ok])
|
47
|
+
href = state.md.normalizeLink.call(res[:str])
|
48
|
+
if (state.md.validateLink.call(href))
|
49
|
+
pos = res[:pos]
|
50
|
+
else
|
51
|
+
href = ''
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# [link]( <href> "title" )
|
56
|
+
# ^^ skipping these spaces
|
57
|
+
start = pos
|
58
|
+
while pos < max
|
59
|
+
code = state.src.charCodeAt(pos)
|
60
|
+
break if (code != 0x20 && code != 0x0A)
|
61
|
+
pos += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# [link]( <href> "title" )
|
65
|
+
# ^^^^^^^ parsing link title
|
66
|
+
res = parseLinkTitle(state.src, pos, state.posMax)
|
67
|
+
if (pos < max && start != pos && res[:ok])
|
68
|
+
title = res[:str]
|
69
|
+
pos = res[:pos]
|
70
|
+
|
71
|
+
# [link]( <href> "title" )
|
72
|
+
# ^^ skipping these spaces
|
73
|
+
while pos < max
|
74
|
+
code = state.src.charCodeAt(pos)
|
75
|
+
break if (code != 0x20 && code != 0x0A)
|
76
|
+
pos += 1
|
77
|
+
end
|
78
|
+
else
|
79
|
+
title = ''
|
80
|
+
end
|
81
|
+
|
82
|
+
if (pos >= max || state.src.charCodeAt(pos) != 0x29) # )
|
83
|
+
state.pos = oldPos
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
pos += 1
|
87
|
+
else
|
88
|
+
#
|
89
|
+
# Link reference
|
90
|
+
#
|
91
|
+
return false if state.env[:references].nil?
|
92
|
+
|
93
|
+
# [foo] [bar]
|
94
|
+
# ^^ optional whitespace (can include newlines)
|
95
|
+
while pos < max
|
96
|
+
code = state.src.charCodeAt(pos);
|
97
|
+
break if (code != 0x20 && code != 0x0A)
|
98
|
+
pos += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
if (pos < max && state.src.charCodeAt(pos) == 0x5B) # [
|
102
|
+
start = pos + 1
|
103
|
+
pos = parseLinkLabel(state, pos)
|
104
|
+
if (pos >= 0)
|
105
|
+
label = state.src.slice(start...pos)
|
106
|
+
pos += 1
|
107
|
+
else
|
108
|
+
pos = labelEnd + 1
|
109
|
+
end
|
110
|
+
else
|
111
|
+
pos = labelEnd + 1
|
112
|
+
end
|
113
|
+
|
114
|
+
# covers label === '' and label === undefined
|
115
|
+
# (collapsed reference link and shortcut reference link respectively)
|
116
|
+
label = state.src.slice(labelStart...labelEnd) if label.nil? || label.empty?
|
117
|
+
|
118
|
+
ref = state.env[:references][normalizeReference(label)]
|
119
|
+
if (!ref)
|
120
|
+
state.pos = oldPos
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
href = ref[:href]
|
124
|
+
title = ref[:title]
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# We found the end of the link, and know for a fact it's a valid link;
|
129
|
+
# so all that's left to do is to call tokenizer.
|
130
|
+
#
|
131
|
+
if (!silent)
|
132
|
+
state.pos = labelStart
|
133
|
+
state.posMax = labelEnd
|
134
|
+
|
135
|
+
token = state.push('link_open', 'a', 1)
|
136
|
+
token.attrs = attrs = [ [ 'href', href ] ]
|
137
|
+
unless title.nil? || title.empty?
|
138
|
+
attrs.push([ 'title', title ])
|
139
|
+
end
|
140
|
+
|
141
|
+
state.md.inline.tokenize(state)
|
142
|
+
|
143
|
+
token = state.push('link_close', 'a', -1)
|
144
|
+
end
|
145
|
+
|
146
|
+
state.pos = pos
|
147
|
+
state.posMax = max
|
148
|
+
return true
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|