motion-markdown-it 0.4.0.3.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 +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,138 @@
|
|
1
|
+
# Block quotes
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class Blockquote
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.blockquote(state, startLine, endLine, silent)
|
9
|
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
10
|
+
max = state.eMarks[startLine]
|
11
|
+
|
12
|
+
# check the block quote marker
|
13
|
+
return false if state.src.charCodeAt(pos) != 0x3E # >
|
14
|
+
pos += 1
|
15
|
+
|
16
|
+
# we know that it's going to be a valid blockquote,
|
17
|
+
# so no point trying to find the end of it in silent mode
|
18
|
+
return true if silent
|
19
|
+
|
20
|
+
# skip one optional space after '>'
|
21
|
+
pos += 1 if state.src.charCodeAt(pos) == 0x20
|
22
|
+
|
23
|
+
oldIndent = state.blkIndent
|
24
|
+
state.blkIndent = 0
|
25
|
+
|
26
|
+
oldBMarks = [ state.bMarks[startLine] ]
|
27
|
+
state.bMarks[startLine] = pos
|
28
|
+
|
29
|
+
# check if we have an empty blockquote
|
30
|
+
pos = pos < max ? state.skipSpaces(pos) : pos
|
31
|
+
lastLineEmpty = pos >= max
|
32
|
+
|
33
|
+
oldTShift = [ state.tShift[startLine] ]
|
34
|
+
state.tShift[startLine] = pos - state.bMarks[startLine]
|
35
|
+
|
36
|
+
terminatorRules = state.md.block.ruler.getRules('blockquote')
|
37
|
+
|
38
|
+
# Search the end of the block
|
39
|
+
#
|
40
|
+
# Block ends with either:
|
41
|
+
# 1. an empty line outside:
|
42
|
+
# ```
|
43
|
+
# > test
|
44
|
+
#
|
45
|
+
# ```
|
46
|
+
# 2. an empty line inside:
|
47
|
+
# ```
|
48
|
+
# >
|
49
|
+
# test
|
50
|
+
# ```
|
51
|
+
# 3. another tag
|
52
|
+
# ```
|
53
|
+
# > test
|
54
|
+
# - - -
|
55
|
+
# ```
|
56
|
+
nextLine = startLine + 1
|
57
|
+
while nextLine < endLine
|
58
|
+
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
59
|
+
max = state.eMarks[nextLine]
|
60
|
+
|
61
|
+
if pos >= max
|
62
|
+
# Case 1: line is not inside the blockquote, and this line is empty.
|
63
|
+
break
|
64
|
+
end
|
65
|
+
|
66
|
+
if state.src.charCodeAt(pos) == 0x3E # >
|
67
|
+
pos += 1
|
68
|
+
# This line is inside the blockquote.
|
69
|
+
|
70
|
+
# skip one optional space after '>'
|
71
|
+
pos += 1 if state.src.charCodeAt(pos) == 0x20
|
72
|
+
|
73
|
+
oldBMarks.push(state.bMarks[nextLine])
|
74
|
+
state.bMarks[nextLine] = pos
|
75
|
+
|
76
|
+
pos = pos < max ? state.skipSpaces(pos) : pos
|
77
|
+
lastLineEmpty = pos >= max
|
78
|
+
|
79
|
+
oldTShift.push(state.tShift[nextLine])
|
80
|
+
state.tShift[nextLine] = pos - state.bMarks[nextLine]
|
81
|
+
nextLine += 1
|
82
|
+
next
|
83
|
+
else
|
84
|
+
pos += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
# Case 2: line is not inside the blockquote, and the last line was empty.
|
88
|
+
break if lastLineEmpty
|
89
|
+
|
90
|
+
# Case 3: another tag found.
|
91
|
+
terminate = false
|
92
|
+
(0...terminatorRules.length).each do |i|
|
93
|
+
if terminatorRules[i].call(state, nextLine, endLine, true)
|
94
|
+
terminate = true
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
break if terminate
|
99
|
+
|
100
|
+
oldBMarks.push(state.bMarks[nextLine])
|
101
|
+
oldTShift.push(state.tShift[nextLine])
|
102
|
+
|
103
|
+
# A negative number means that this is a paragraph continuation
|
104
|
+
#
|
105
|
+
# Any negative number will do the job here, but it's better for it
|
106
|
+
# to be large enough to make any bugs obvious.
|
107
|
+
state.tShift[nextLine] = -1337
|
108
|
+
nextLine += 1
|
109
|
+
end
|
110
|
+
|
111
|
+
oldParentType = state.parentType
|
112
|
+
state.parentType = 'blockquote'
|
113
|
+
|
114
|
+
token = state.push('blockquote_open', 'blockquote', 1)
|
115
|
+
token.markup = '>'
|
116
|
+
token.map = lines = [ startLine, 0 ]
|
117
|
+
|
118
|
+
state.md.block.tokenize(state, startLine, nextLine)
|
119
|
+
|
120
|
+
token = state.push('blockquote_close', 'blockquote', -1)
|
121
|
+
token.markup = '>'
|
122
|
+
|
123
|
+
state.parentType = oldParentType
|
124
|
+
lines[1] = state.line
|
125
|
+
|
126
|
+
# Restore original tShift; this might not be necessary since the parser
|
127
|
+
# has already been here, but just to make sure we can do that.
|
128
|
+
(0...oldTShift.length).each do |i|
|
129
|
+
state.bMarks[i + startLine] = oldBMarks[i]
|
130
|
+
state.tShift[i + startLine] = oldTShift[i]
|
131
|
+
end
|
132
|
+
state.blkIndent = oldIndent
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Code block (4 spaces padded)
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class Code
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.code(state, startLine, endLine, silent = true)
|
9
|
+
return false if (state.tShift[startLine] - state.blkIndent < 4)
|
10
|
+
|
11
|
+
last = nextLine = startLine + 1
|
12
|
+
while nextLine < endLine
|
13
|
+
if state.isEmpty(nextLine)
|
14
|
+
nextLine += 1
|
15
|
+
next
|
16
|
+
end
|
17
|
+
if (state.tShift[nextLine] - state.blkIndent >= 4)
|
18
|
+
nextLine += 1
|
19
|
+
last = nextLine
|
20
|
+
next
|
21
|
+
end
|
22
|
+
break
|
23
|
+
end
|
24
|
+
|
25
|
+
state.line = nextLine
|
26
|
+
|
27
|
+
token = state.push('code_block', 'code', 0)
|
28
|
+
token.content = state.getLines(startLine, last, 4 + state.blkIndent, true)
|
29
|
+
token.map = [ startLine, state.line ]
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# fences (``` lang, ~~~ lang)
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class Fence
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.fence(state, startLine, endLine, silent)
|
9
|
+
haveEndMarker = false
|
10
|
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
11
|
+
max = state.eMarks[startLine]
|
12
|
+
|
13
|
+
return false if pos + 3 > max
|
14
|
+
|
15
|
+
marker = state.src.charCodeAt(pos)
|
16
|
+
|
17
|
+
if marker != 0x7E && marker != 0x60 # != ~ && != `
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
# scan marker length
|
22
|
+
mem = pos;
|
23
|
+
pos = state.skipChars(pos, marker)
|
24
|
+
len = pos - mem
|
25
|
+
|
26
|
+
return false if len < 3
|
27
|
+
|
28
|
+
markup = state.src.slice(mem...pos)
|
29
|
+
params = state.src.slice(pos...max)
|
30
|
+
|
31
|
+
return false if params.include?('`')
|
32
|
+
|
33
|
+
# Since start is found, we can report success here in validation mode
|
34
|
+
return true if silent
|
35
|
+
|
36
|
+
# search end of block
|
37
|
+
nextLine = startLine
|
38
|
+
|
39
|
+
while true
|
40
|
+
nextLine += 1
|
41
|
+
if nextLine >= endLine
|
42
|
+
# unclosed block should be autoclosed by end of document.
|
43
|
+
# also block seems to be autoclosed by end of parent
|
44
|
+
break
|
45
|
+
end
|
46
|
+
|
47
|
+
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
|
48
|
+
max = state.eMarks[nextLine];
|
49
|
+
|
50
|
+
if pos < max && state.tShift[nextLine] < state.blkIndent
|
51
|
+
# non-empty line with negative indent should stop the list:
|
52
|
+
# - ```
|
53
|
+
# test
|
54
|
+
break
|
55
|
+
end
|
56
|
+
|
57
|
+
next if state.src.charCodeAt(pos) != marker
|
58
|
+
|
59
|
+
if state.tShift[nextLine] - state.blkIndent >= 4
|
60
|
+
# closing fence should be indented less than 4 spaces
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
pos = state.skipChars(pos, marker)
|
65
|
+
|
66
|
+
# closing code fence must be at least as long as the opening one
|
67
|
+
next if pos - mem < len
|
68
|
+
|
69
|
+
# make sure tail has spaces only
|
70
|
+
pos = state.skipSpaces(pos)
|
71
|
+
|
72
|
+
next if pos < max
|
73
|
+
|
74
|
+
haveEndMarker = true
|
75
|
+
# found!
|
76
|
+
break
|
77
|
+
end
|
78
|
+
|
79
|
+
# If a fence has heading spaces, they should be removed from its inner block
|
80
|
+
len = state.tShift[startLine]
|
81
|
+
state.line = nextLine + (haveEndMarker ? 1 : 0)
|
82
|
+
|
83
|
+
token = state.push('fence', 'code', 0)
|
84
|
+
token.info = params
|
85
|
+
token.content = state.getLines(startLine + 1, nextLine, len, true)
|
86
|
+
token.markup = markup
|
87
|
+
token.map = [ startLine, state.line ]
|
88
|
+
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# heading (#, ##, ...)
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class Heading
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.heading(state, startLine, endLine, silent)
|
9
|
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
10
|
+
max = state.eMarks[startLine]
|
11
|
+
ch = state.src.charCodeAt(pos)
|
12
|
+
|
13
|
+
return false if (ch != 0x23 || pos >= max)
|
14
|
+
|
15
|
+
# count heading level
|
16
|
+
level = 1
|
17
|
+
pos += 1
|
18
|
+
ch = state.src.charCodeAt(pos)
|
19
|
+
while (ch == 0x23 && pos < max && level <= 6) # '#'
|
20
|
+
level += 1
|
21
|
+
pos += 1
|
22
|
+
ch = state.src.charCodeAt(pos)
|
23
|
+
end
|
24
|
+
|
25
|
+
return false if (level > 6 || (pos < max && ch != 0x20)) # space
|
26
|
+
|
27
|
+
return true if (silent)
|
28
|
+
|
29
|
+
# Let's cut tails like ' ### ' from the end of string
|
30
|
+
|
31
|
+
max = state.skipCharsBack(max, 0x20, pos) # space
|
32
|
+
tmp = state.skipCharsBack(max, 0x23, pos) # '#'
|
33
|
+
if (tmp > pos && state.src.charCodeAt(tmp - 1) == 0x20) # space
|
34
|
+
max = tmp
|
35
|
+
end
|
36
|
+
|
37
|
+
state.line = startLine + 1
|
38
|
+
|
39
|
+
token = state.push('heading_open', "h#{level.to_s}", 1)
|
40
|
+
token.markup = '########'.slice(0...level)
|
41
|
+
token.map = [ startLine, state.line ]
|
42
|
+
|
43
|
+
token = state.push('inline', '', 0)
|
44
|
+
token.content = state.src.slice(pos...max).strip
|
45
|
+
token.map = [ startLine, state.line ]
|
46
|
+
token.children = []
|
47
|
+
|
48
|
+
token = state.push('heading_close', "h#{level.to_s}", -1)
|
49
|
+
token.markup = '########'.slice(0...level)
|
50
|
+
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Horizontal rule
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class Hr
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.hr(state, startLine, endLine, silent)
|
9
|
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
10
|
+
max = state.eMarks[startLine]
|
11
|
+
marker = state.src.charCodeAt(pos)
|
12
|
+
pos += 1
|
13
|
+
|
14
|
+
# Check hr marker
|
15
|
+
if (marker != 0x2A && # *
|
16
|
+
marker != 0x2D && # -
|
17
|
+
marker != 0x5F) # _
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
# markers can be mixed with spaces, but there should be at least 3 one
|
22
|
+
|
23
|
+
cnt = 1
|
24
|
+
while (pos < max)
|
25
|
+
ch = state.src.charCodeAt(pos)
|
26
|
+
pos += 1
|
27
|
+
return false if (ch != marker && ch != 0x20) # space
|
28
|
+
cnt += 1 if (ch == marker)
|
29
|
+
end
|
30
|
+
|
31
|
+
return false if (cnt < 3)
|
32
|
+
return true if (silent)
|
33
|
+
|
34
|
+
state.line = startLine + 1
|
35
|
+
|
36
|
+
token = state.push('hr', 'hr', 0)
|
37
|
+
token.map = [ startLine, state.line ]
|
38
|
+
token.markup = marker.chr * (cnt + 1)
|
39
|
+
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# HTML block
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesBlock
|
5
|
+
class HtmlBlock
|
6
|
+
|
7
|
+
HTML_TAG_OPEN_RE = /^<([a-zA-Z][a-zA-Z0-9]{0,14})[\s\/>]/
|
8
|
+
HTML_TAG_CLOSE_RE = /^<\/([a-zA-Z][a-zA-Z0-9]{0,14})[\s>]/
|
9
|
+
|
10
|
+
#------------------------------------------------------------------------------
|
11
|
+
def self.isLetter(ch)
|
12
|
+
lc = ch | 0x20; # to lower case
|
13
|
+
return (lc >= 0x61) && (lc <= 0x7a) # >= a and <= z
|
14
|
+
end
|
15
|
+
|
16
|
+
#------------------------------------------------------------------------------
|
17
|
+
def self.html_block(state, startLine, endLine, silent)
|
18
|
+
pos = state.bMarks[startLine]
|
19
|
+
max = state.eMarks[startLine]
|
20
|
+
shift = state.tShift[startLine]
|
21
|
+
|
22
|
+
pos += shift
|
23
|
+
|
24
|
+
return false if !state.md.options[:html]
|
25
|
+
return false if shift > 3 || (pos + 2) >= max
|
26
|
+
return false if state.src.charCodeAt(pos) != 0x3C # <
|
27
|
+
|
28
|
+
ch = state.src.charCodeAt(pos + 1)
|
29
|
+
|
30
|
+
if ch == 0x21 || ch == 0x3F # ! or ?
|
31
|
+
# Directive start / comment start / processing instruction start
|
32
|
+
return true if silent
|
33
|
+
|
34
|
+
elsif ch == 0x2F || isLetter(ch) # /
|
35
|
+
|
36
|
+
# Probably start or end of tag
|
37
|
+
if ch == 0x2F # \
|
38
|
+
# closing tag
|
39
|
+
match = state.src.slice(pos...max).match(HTML_TAG_CLOSE_RE)
|
40
|
+
return false if (!match)
|
41
|
+
else
|
42
|
+
# opening tag
|
43
|
+
match = state.src.slice(pos...max).match(HTML_TAG_OPEN_RE)
|
44
|
+
return false if !match
|
45
|
+
end
|
46
|
+
|
47
|
+
# Make sure tag name is valid
|
48
|
+
return false if HTML_BLOCKS[match[1].downcase] != true
|
49
|
+
return true if silent
|
50
|
+
|
51
|
+
else
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
# If we are here - we detected HTML block.
|
56
|
+
# Let's roll down till empty line (block end).
|
57
|
+
nextLine = startLine + 1
|
58
|
+
while nextLine < state.lineMax && !state.isEmpty(nextLine)
|
59
|
+
nextLine += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
state.line = nextLine
|
63
|
+
|
64
|
+
token = state.push('html_block', '', 0)
|
65
|
+
token.map = [ startLine, state.line ]
|
66
|
+
token.content = state.getLines(startLine, nextLine, 0, true)
|
67
|
+
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|