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.
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