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