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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +243 -0
  3. data/lib/motion-markdown-it.rb +71 -0
  4. data/lib/motion-markdown-it/common/entities.rb +1084 -0
  5. data/lib/motion-markdown-it/common/html_blocks.rb +60 -0
  6. data/lib/motion-markdown-it/common/html_re.rb +28 -0
  7. data/lib/motion-markdown-it/common/string.rb +14 -0
  8. data/lib/motion-markdown-it/common/url_schemas.rb +173 -0
  9. data/lib/motion-markdown-it/common/utils.rb +216 -0
  10. data/lib/motion-markdown-it/helpers/parse_link_destination.rb +75 -0
  11. data/lib/motion-markdown-it/helpers/parse_link_label.rb +51 -0
  12. data/lib/motion-markdown-it/helpers/parse_link_title.rb +48 -0
  13. data/lib/motion-markdown-it/index.rb +507 -0
  14. data/lib/motion-markdown-it/parser_block.rb +113 -0
  15. data/lib/motion-markdown-it/parser_core.rb +46 -0
  16. data/lib/motion-markdown-it/parser_inline.rb +121 -0
  17. data/lib/motion-markdown-it/presets/commonmark.rb +76 -0
  18. data/lib/motion-markdown-it/presets/default.rb +42 -0
  19. data/lib/motion-markdown-it/presets/zero.rb +59 -0
  20. data/lib/motion-markdown-it/renderer.rb +286 -0
  21. data/lib/motion-markdown-it/ruler.rb +327 -0
  22. data/lib/motion-markdown-it/rules_block/blockquote.rb +138 -0
  23. data/lib/motion-markdown-it/rules_block/code.rb +35 -0
  24. data/lib/motion-markdown-it/rules_block/fence.rb +94 -0
  25. data/lib/motion-markdown-it/rules_block/heading.rb +56 -0
  26. data/lib/motion-markdown-it/rules_block/hr.rb +45 -0
  27. data/lib/motion-markdown-it/rules_block/html_block.rb +73 -0
  28. data/lib/motion-markdown-it/rules_block/lheading.rb +54 -0
  29. data/lib/motion-markdown-it/rules_block/list.rb +242 -0
  30. data/lib/motion-markdown-it/rules_block/paragraph.rb +51 -0
  31. data/lib/motion-markdown-it/rules_block/reference.rb +161 -0
  32. data/lib/motion-markdown-it/rules_block/state_block.rb +184 -0
  33. data/lib/motion-markdown-it/rules_block/table.rb +161 -0
  34. data/lib/motion-markdown-it/rules_core/block.rb +20 -0
  35. data/lib/motion-markdown-it/rules_core/inline.rb +20 -0
  36. data/lib/motion-markdown-it/rules_core/linkify.rb +138 -0
  37. data/lib/motion-markdown-it/rules_core/normalize.rb +44 -0
  38. data/lib/motion-markdown-it/rules_core/replacements.rb +90 -0
  39. data/lib/motion-markdown-it/rules_core/smartquotes.rb +158 -0
  40. data/lib/motion-markdown-it/rules_core/state_core.rb +20 -0
  41. data/lib/motion-markdown-it/rules_inline/autolink.rb +74 -0
  42. data/lib/motion-markdown-it/rules_inline/backticks.rb +51 -0
  43. data/lib/motion-markdown-it/rules_inline/emphasis.rb +172 -0
  44. data/lib/motion-markdown-it/rules_inline/entity.rb +51 -0
  45. data/lib/motion-markdown-it/rules_inline/escape.rb +55 -0
  46. data/lib/motion-markdown-it/rules_inline/html_inline.rb +49 -0
  47. data/lib/motion-markdown-it/rules_inline/image.rb +158 -0
  48. data/lib/motion-markdown-it/rules_inline/link.rb +153 -0
  49. data/lib/motion-markdown-it/rules_inline/newline.rb +47 -0
  50. data/lib/motion-markdown-it/rules_inline/state_inline.rb +57 -0
  51. data/lib/motion-markdown-it/rules_inline/strikethrough.rb +130 -0
  52. data/lib/motion-markdown-it/rules_inline/text.rb +94 -0
  53. data/lib/motion-markdown-it/token.rb +134 -0
  54. data/lib/motion-markdown-it/version.rb +5 -0
  55. data/spec/motion-markdown-it/bench_mark_spec.rb +44 -0
  56. data/spec/motion-markdown-it/commonmark_spec.rb +16 -0
  57. data/spec/motion-markdown-it/markdown_it_spec.rb +18 -0
  58. data/spec/motion-markdown-it/misc_spec.rb +277 -0
  59. data/spec/motion-markdown-it/ruler_spec.rb +153 -0
  60. data/spec/motion-markdown-it/testgen_helper.rb +68 -0
  61. data/spec/motion-markdown-it/token_spec.rb +17 -0
  62. data/spec/motion-markdown-it/utils_spec.rb +82 -0
  63. data/spec/spec_helper.rb +6 -0
  64. metadata +158 -0
@@ -0,0 +1,54 @@
1
+ # lheading (---, ===)
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesBlock
5
+ class Lheading
6
+
7
+ #------------------------------------------------------------------------------
8
+ def self.lheading(state, startLine, endLine, silent = true)
9
+ nextLine = startLine + 1
10
+
11
+ return false if (nextLine >= endLine)
12
+ return false if (state.tShift[nextLine] < state.blkIndent)
13
+
14
+ # Scan next line
15
+
16
+ return false if (state.tShift[nextLine] - state.blkIndent > 3)
17
+
18
+ pos = state.bMarks[nextLine] + state.tShift[nextLine]
19
+ max = state.eMarks[nextLine]
20
+
21
+ return false if (pos >= max)
22
+
23
+ marker = state.src.charCodeAt(pos)
24
+
25
+ return false if (marker != 0x2D && marker != 0x3D) # != '-' && != '='
26
+
27
+ pos = state.skipChars(pos, marker)
28
+ pos = state.skipSpaces(pos)
29
+
30
+ return false if (pos < max)
31
+
32
+ pos = state.bMarks[startLine] + state.tShift[startLine]
33
+
34
+ state.line = nextLine + 1
35
+ level = (marker == 0x3D ? 1 : 2) # =
36
+
37
+ token = state.push('heading_open', "h#{level.to_s}", 1)
38
+ token.markup = marker.chr
39
+ token.map = [ startLine, state.line ]
40
+
41
+ token = state.push('inline', '', 0)
42
+ token.content = state.src.slice(pos...state.eMarks[startLine]).strip
43
+ token.map = [ startLine, state.line - 1 ]
44
+ token.children = []
45
+
46
+ token = state.push('heading_close', "h#{level.to_s}", -1)
47
+ token.markup = marker.chr
48
+
49
+ return true
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,242 @@
1
+ # Lists
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesBlock
5
+ class List
6
+
7
+ # Search `[-+*][\n ]`, returns next pos arter marker on success
8
+ # or -1 on fail.
9
+ #------------------------------------------------------------------------------
10
+ def self.skipBulletListMarker(state, startLine)
11
+ pos = state.bMarks[startLine] + state.tShift[startLine]
12
+ max = state.eMarks[startLine]
13
+
14
+ marker = state.src.charCodeAt(pos)
15
+ pos += 1
16
+ # Check bullet
17
+ if (marker != 0x2A && # *
18
+ marker != 0x2D && # -
19
+ marker != 0x2B) # +
20
+ return -1
21
+ end
22
+
23
+ if (pos < max && state.src.charCodeAt(pos) != 0x20)
24
+ # " 1.test " - is not a list item
25
+ return -1
26
+ end
27
+
28
+ return pos
29
+ end
30
+
31
+ # Search `\d+[.)][\n ]`, returns next pos after marker on success
32
+ # or -1 on fail.
33
+ #------------------------------------------------------------------------------
34
+ def self.skipOrderedListMarker(state, startLine)
35
+ pos = state.bMarks[startLine] + state.tShift[startLine]
36
+ max = state.eMarks[startLine]
37
+
38
+ # List marker should have at least 2 chars (digit + dot)
39
+ return -1 if (pos + 1 >= max)
40
+
41
+ ch = state.src.charCodeAt(pos)
42
+ pos += 1
43
+
44
+ return -1 if ch.nil?
45
+ return -1 if (ch < 0x30 || ch > 0x39) # < 0 || > 9
46
+
47
+ while true
48
+ # EOL -> fail
49
+ return -1 if (pos >= max)
50
+
51
+ ch = state.src.charCodeAt(pos)
52
+ pos += 1
53
+
54
+ if (ch >= 0x30 && ch <= 0x39) # >= 0 && <= 9
55
+ next
56
+ end
57
+
58
+ # found valid marker
59
+ if (ch === 0x29 || ch === 0x2e) # ')' || '.'
60
+ break
61
+ end
62
+
63
+ return -1
64
+ end
65
+
66
+
67
+ if (pos < max && state.src.charCodeAt(pos) != 0x20) # space
68
+ # " 1.test " - is not a list item
69
+ return -1
70
+ end
71
+ return pos
72
+ end
73
+
74
+ #------------------------------------------------------------------------------
75
+ def self.markTightParagraphs(state, idx)
76
+ level = state.level + 2
77
+
78
+ i = idx + 2
79
+ l = state.tokens.length
80
+ while i < l
81
+ if (state.tokens[i].level == level && state.tokens[i].type == 'paragraph_open')
82
+ state.tokens[i + 2].hidden = true
83
+ state.tokens[i].hidden = true
84
+ i += 2
85
+ end
86
+ i += 1
87
+ end
88
+ end
89
+
90
+
91
+ #------------------------------------------------------------------------------
92
+ def self.list(state, startLine, endLine, silent)
93
+ tight = true
94
+
95
+ # Detect list type and position after marker
96
+ if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0)
97
+ isOrdered = true
98
+ elsif ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0)
99
+ isOrdered = false
100
+ else
101
+ return false
102
+ end
103
+
104
+ # We should terminate list on style change. Remember first one to compare.
105
+ markerCharCode = state.src.charCodeAt(posAfterMarker - 1)
106
+
107
+ # For validation mode we can terminate immediately
108
+ return true if (silent)
109
+
110
+ # Start list
111
+ listTokIdx = state.tokens.length
112
+
113
+ if (isOrdered)
114
+ start = state.bMarks[startLine] + state.tShift[startLine]
115
+ markerValue = state.src[start, posAfterMarker - start - 1].to_i
116
+ token = state.push('ordered_list_open', 'ol', 1)
117
+ if (markerValue > 1)
118
+ token.attrs = [ [ 'start', markerValue ] ]
119
+ end
120
+
121
+ else
122
+ token = state.push('bullet_list_open', 'ul', 1)
123
+ end
124
+
125
+ token.map = listLines = [ startLine, 0 ]
126
+ token.markup = markerCharCode.chr
127
+
128
+ #
129
+ # Iterate list items
130
+ #
131
+
132
+ nextLine = startLine
133
+ prevEmptyEnd = false
134
+ terminatorRules = state.md.block.ruler.getRules('list')
135
+
136
+ while (nextLine < endLine)
137
+ contentStart = state.skipSpaces(posAfterMarker)
138
+ max = state.eMarks[nextLine]
139
+
140
+ if (contentStart >= max)
141
+ # trimming space in "- \n 3" case, indent is 1 here
142
+ indentAfterMarker = 1
143
+ else
144
+ indentAfterMarker = contentStart - posAfterMarker
145
+ end
146
+
147
+ # If we have more than 4 spaces, the indent is 1
148
+ # (the rest is just indented code block)
149
+ indentAfterMarker = 1 if (indentAfterMarker > 4)
150
+
151
+ # " - test"
152
+ # ^^^^^ - calculating total length of this thing
153
+ indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker
154
+
155
+ # Run subparser & write tokens
156
+ token = state.push('list_item_open', 'li', 1)
157
+ token.markup = markerCharCode.chr
158
+ token.map = itemLines = [ startLine, 0 ]
159
+
160
+ oldIndent = state.blkIndent
161
+ oldTight = state.tight
162
+ oldTShift = state.tShift[startLine]
163
+ oldParentType = state.parentType
164
+ state.tShift[startLine] = contentStart - state.bMarks[startLine]
165
+ state.blkIndent = indent
166
+ state.tight = true
167
+ state.parentType = 'list'
168
+
169
+ state.md.block.tokenize(state, startLine, endLine, true)
170
+
171
+ # If any of list item is tight, mark list as tight
172
+ if (!state.tight || prevEmptyEnd)
173
+ tight = false
174
+ end
175
+ # Item become loose if finish with empty line,
176
+ # but we should filter last element, because it means list finish
177
+ prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1)
178
+
179
+ state.blkIndent = oldIndent
180
+ state.tShift[startLine] = oldTShift
181
+ state.tight = oldTight
182
+ state.parentType = oldParentType
183
+
184
+ token = state.push('list_item_close', 'li', -1)
185
+ token.markup = markerCharCode.chr
186
+
187
+ nextLine = startLine = state.line
188
+ itemLines[1] = nextLine
189
+ contentStart = state.bMarks[startLine]
190
+
191
+ break if (nextLine >= endLine)
192
+ break if (state.isEmpty(nextLine))
193
+
194
+ #
195
+ # Try to check if list is terminated or continued.
196
+ #
197
+ break if (state.tShift[nextLine] < state.blkIndent)
198
+
199
+ # fail if terminating block found
200
+ terminate = false
201
+ (0...terminatorRules.length).each do |i|
202
+ if (terminatorRules[i].call(state, nextLine, endLine, true))
203
+ terminate = true
204
+ break
205
+ end
206
+ end
207
+ break if (terminate)
208
+
209
+ # fail if list has another type
210
+ if (isOrdered)
211
+ posAfterMarker = skipOrderedListMarker(state, nextLine)
212
+ break if (posAfterMarker < 0)
213
+ else
214
+ posAfterMarker = skipBulletListMarker(state, nextLine)
215
+ break if (posAfterMarker < 0)
216
+ end
217
+
218
+ break if (markerCharCode != state.src.charCodeAt(posAfterMarker - 1))
219
+ end
220
+
221
+ # Finilize list
222
+ if (isOrdered)
223
+ token = state.push('ordered_list_close', 'ol', -1)
224
+ else
225
+ token = state.push('bullet_list_close', 'ul', -1)
226
+ end
227
+ token.markup = markerCharCode.chr
228
+
229
+ listLines[1] = nextLine
230
+ state.line = nextLine
231
+
232
+ # mark paragraphs tight if needed
233
+ if (tight)
234
+ markTightParagraphs(state, listTokIdx)
235
+ end
236
+
237
+ return true
238
+ end
239
+
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,51 @@
1
+ # Paragraph
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesBlock
5
+ class Paragraph
6
+
7
+ #------------------------------------------------------------------------------
8
+ def self.paragraph(state, startLine)
9
+ nextLine = startLine + 1
10
+ terminatorRules = state.md.block.ruler.getRules('paragraph')
11
+ endLine = state.lineMax
12
+
13
+ # jump line-by-line until empty one or EOF
14
+ # for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
15
+ while nextLine < endLine && !state.isEmpty(nextLine)
16
+ # this would be a code block normally, but after paragraph
17
+ # it's considered a lazy continuation regardless of what's there
18
+ (nextLine += 1) && next if (state.tShift[nextLine] - state.blkIndent > 3)
19
+
20
+ # Some tags can terminate paragraph without empty line.
21
+ terminate = false
22
+ 0.upto(terminatorRules.length - 1) do |i|
23
+ if terminatorRules[i].call(state, nextLine, endLine, true)
24
+ terminate = true
25
+ break
26
+ end
27
+ end
28
+ break if terminate
29
+ nextLine += 1
30
+ end
31
+
32
+ content = state.getLines(startLine, nextLine, state.blkIndent, false).strip
33
+
34
+ state.line = nextLine
35
+
36
+ token = state.push('paragraph_open', 'p', 1)
37
+ token.map = [ startLine, state.line ]
38
+
39
+ token = state.push('inline', '', 0)
40
+ token.content = content
41
+ token.map = [ startLine, state.line ]
42
+ token.children = []
43
+
44
+ token = state.push('paragraph_close', 'p', -1)
45
+
46
+ return true
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,161 @@
1
+ module MarkdownIt
2
+ module RulesBlock
3
+ class Reference
4
+ extend Helpers::ParseLinkDestination
5
+ extend Helpers::ParseLinkTitle
6
+ extend Common::Utils
7
+
8
+ #------------------------------------------------------------------------------
9
+ def self.reference(state, startLine, _endLine, silent)
10
+ lines = 0
11
+ pos = state.bMarks[startLine] + state.tShift[startLine]
12
+ max = state.eMarks[startLine]
13
+ nextLine = startLine + 1
14
+
15
+ return false if (state.src.charCodeAt(pos) != 0x5B) # [
16
+
17
+ # Simple check to quickly interrupt scan on [link](url) at the start of line.
18
+ # Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54
19
+ pos += 1
20
+ while (pos < max)
21
+ if (state.src.charCodeAt(pos) == 0x5D && # ]
22
+ state.src.charCodeAt(pos - 1) != 0x5C) # \
23
+ return false if (pos + 1 === max)
24
+ return false if (state.src.charCodeAt(pos + 1) != 0x3A) # :
25
+ break
26
+ end
27
+ pos += 1
28
+ end
29
+
30
+ endLine = state.lineMax
31
+
32
+ # jump line-by-line until empty one or EOF
33
+ terminatorRules = state.md.block.ruler.getRules('reference')
34
+
35
+ while nextLine < endLine && !state.isEmpty(nextLine)
36
+ nextLine += 1
37
+ # this would be a code block normally, but after paragraph
38
+ # it's considered a lazy continuation regardless of what's there
39
+ next if (state.tShift[nextLine] - state.blkIndent > 3)
40
+
41
+ # Some tags can terminate paragraph without empty line.
42
+ terminate = false
43
+ (0...terminatorRules.length).each do |i|
44
+ if (terminatorRules[i].call(state, nextLine, endLine, true))
45
+ terminate = true
46
+ break
47
+ end
48
+ end
49
+ break if (terminate)
50
+ end
51
+
52
+ str = state.getLines(startLine, nextLine, state.blkIndent, false).strip
53
+ max = str.length
54
+ labelEnd = -1
55
+
56
+ pos = 1
57
+ while pos < max
58
+ ch = str.charCodeAt(pos)
59
+ if (ch == 0x5B ) # [
60
+ return false
61
+ elsif (ch == 0x5D) # ]
62
+ labelEnd = pos
63
+ break
64
+ elsif (ch == 0x0A) # \n
65
+ lines += 1
66
+ elsif (ch == 0x5C) # \
67
+ pos += 1
68
+ if (pos < max && str.charCodeAt(pos) == 0x0A)
69
+ lines += 1
70
+ end
71
+ end
72
+ pos += 1
73
+ end
74
+
75
+ return false if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) != 0x3A) # :
76
+
77
+ # [label]: destination 'title'
78
+ # ^^^ skip optional whitespace here
79
+ pos = labelEnd + 2
80
+ while pos < max
81
+ ch = str.charCodeAt(pos)
82
+ if (ch == 0x0A)
83
+ lines += 1
84
+ elsif (ch == 0x20)
85
+ else
86
+ break
87
+ end
88
+ pos += 1
89
+ end
90
+
91
+ # [label]: destination 'title'
92
+ # ^^^^^^^^^^^ parse this
93
+ res = parseLinkDestination(str, pos, max)
94
+ return false if (!res[:ok])
95
+
96
+ href = state.md.normalizeLink.call(res[:str])
97
+ return false if (!state.md.validateLink.call(href))
98
+
99
+ pos = res[:pos]
100
+ lines += res[:lines]
101
+
102
+ # save cursor state, we could require to rollback later
103
+ destEndPos = pos
104
+ destEndLineNo = lines
105
+
106
+ # [label]: destination 'title'
107
+ # ^^^ skipping those spaces
108
+ start = pos
109
+ while (pos < max)
110
+ ch = str.charCodeAt(pos)
111
+ if (ch == 0x0A)
112
+ lines += 1
113
+ elsif (ch == 0x20)
114
+ else
115
+ break
116
+ end
117
+ pos += 1
118
+ end
119
+
120
+ # [label]: destination 'title'
121
+ # ^^^^^^^ parse this
122
+ res = parseLinkTitle(str, pos, max)
123
+ if (pos < max && start != pos && res[:ok])
124
+ title = res[:str]
125
+ pos = res[:pos]
126
+ lines += res[:lines]
127
+ else
128
+ title = ''
129
+ pos = destEndPos
130
+ lines = destEndLineNo
131
+ end
132
+
133
+ # skip trailing spaces until the rest of the line
134
+ while (pos < max && str.charCodeAt(pos) == 0x20) # space
135
+ pos += 1
136
+ end
137
+
138
+ if (pos < max && str.charCodeAt(pos) != 0x0A)
139
+ # garbage at the end of the line
140
+ return false
141
+ end
142
+
143
+ # Reference can not terminate anything. This check is for safety only.
144
+ # istanbul ignore if
145
+ return true if (silent)
146
+
147
+ label = normalizeReference(str.slice(1...labelEnd))
148
+ if (state.env[:references].nil?)
149
+ state.env[:references] = {}
150
+ end
151
+ if state.env[:references][label].nil?
152
+ state.env[:references][label] = { title: title, href: href }
153
+ end
154
+
155
+ state.line = startLine + lines + 1
156
+ return true
157
+ end
158
+
159
+ end
160
+ end
161
+ end