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,47 @@
|
|
1
|
+
# Proceess '\n'
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Newline
|
6
|
+
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
def self.newline(state, silent)
|
9
|
+
pos = state.pos
|
10
|
+
return false if state.src.charCodeAt(pos) != 0x0A # \n
|
11
|
+
|
12
|
+
pmax = state.pending.length - 1
|
13
|
+
max = state.posMax
|
14
|
+
|
15
|
+
# ' \n' -> hardbreak
|
16
|
+
# Lookup in pending chars is bad practice! Don't copy to other rules!
|
17
|
+
# Pending string is stored in concat mode, indexed lookups will cause
|
18
|
+
# convertion to flat mode.
|
19
|
+
if !silent
|
20
|
+
if pmax >= 0 && state.pending.charCodeAt(pmax) == 0x20
|
21
|
+
if pmax >= 1 && state.pending.charCodeAt(pmax - 1) == 0x20
|
22
|
+
state.pending = state.pending.sub(/ +$/, '')
|
23
|
+
state.push('hardbreak', 'br', 0)
|
24
|
+
else
|
25
|
+
state.pending = state.pending.slice(0...-1)
|
26
|
+
state.push('softbreak', 'br', 0)
|
27
|
+
end
|
28
|
+
|
29
|
+
else
|
30
|
+
state.push('softbreak', 'br', 0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
pos += 1
|
35
|
+
|
36
|
+
# skip heading spaces for next line
|
37
|
+
while pos < max && state.src.charCodeAt(pos) == 0x20
|
38
|
+
pos += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
state.pos = pos
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Inline parser state
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class StateInline
|
6
|
+
|
7
|
+
attr_accessor :src, :env, :md, :tokens, :pos, :posMax, :level
|
8
|
+
attr_accessor :pending, :pendingLevel, :cache
|
9
|
+
|
10
|
+
#------------------------------------------------------------------------------
|
11
|
+
def initialize(src, md, env, outTokens)
|
12
|
+
@src = src
|
13
|
+
@env = env
|
14
|
+
@md = md
|
15
|
+
@tokens = outTokens
|
16
|
+
|
17
|
+
@pos = 0
|
18
|
+
@posMax = @src.length
|
19
|
+
@level = 0
|
20
|
+
@pending = ''
|
21
|
+
@pendingLevel = 0
|
22
|
+
|
23
|
+
@cache = {} # Stores { start: end } pairs. Useful for backtrack
|
24
|
+
# optimization of pairs parse (emphasis, strikes).
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Flush pending text
|
29
|
+
#------------------------------------------------------------------------------
|
30
|
+
def pushPending
|
31
|
+
token = Token.new('text', '', 0)
|
32
|
+
token.content = @pending
|
33
|
+
token.level = @pendingLevel
|
34
|
+
@tokens.push(token)
|
35
|
+
@pending = ''
|
36
|
+
return token
|
37
|
+
end
|
38
|
+
|
39
|
+
# Push new token to "stream".
|
40
|
+
# If pending text exists - flush it as text token
|
41
|
+
#------------------------------------------------------------------------------
|
42
|
+
def push(type, tag, nesting)
|
43
|
+
pushPending unless @pending.empty?
|
44
|
+
|
45
|
+
token = Token.new(type, tag, nesting);
|
46
|
+
@level -= 1 if nesting < 0
|
47
|
+
token.level = @level
|
48
|
+
@level += 1 if nesting > 0
|
49
|
+
|
50
|
+
@pendingLevel = @level
|
51
|
+
@tokens.push(token)
|
52
|
+
return token
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# ~~strike through~~
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
module RulesInline
|
5
|
+
class Strikethrough
|
6
|
+
extend Common::Utils
|
7
|
+
|
8
|
+
# parse sequence of markers,
|
9
|
+
# "start" should point at a valid marker
|
10
|
+
def self.scanDelims(state, start)
|
11
|
+
pos = start
|
12
|
+
can_open = true
|
13
|
+
can_close = true
|
14
|
+
max = state.posMax
|
15
|
+
marker = state.src.charCodeAt(start)
|
16
|
+
|
17
|
+
# treat beginning of the line as a whitespace
|
18
|
+
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20
|
19
|
+
|
20
|
+
while (pos < max && state.src.charCodeAt(pos) == marker)
|
21
|
+
pos += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
if (pos >= max)
|
25
|
+
can_open = false
|
26
|
+
end
|
27
|
+
|
28
|
+
count = pos - start
|
29
|
+
|
30
|
+
# treat end of the line as a whitespace
|
31
|
+
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20
|
32
|
+
|
33
|
+
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(lastChar.chr)
|
34
|
+
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(nextChar.chr)
|
35
|
+
|
36
|
+
isLastWhiteSpace = isWhiteSpace(lastChar)
|
37
|
+
isNextWhiteSpace = isWhiteSpace(nextChar)
|
38
|
+
|
39
|
+
if (isNextWhiteSpace)
|
40
|
+
can_open = false
|
41
|
+
elsif (isNextPunctChar)
|
42
|
+
if (!(isLastWhiteSpace || isLastPunctChar))
|
43
|
+
can_open = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if (isLastWhiteSpace)
|
48
|
+
can_close = false
|
49
|
+
elsif (isLastPunctChar)
|
50
|
+
if (!(isNextWhiteSpace || isNextPunctChar))
|
51
|
+
can_close = false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return { can_open: can_open, can_close: can_close, delims: count }
|
56
|
+
end
|
57
|
+
|
58
|
+
#------------------------------------------------------------------------------
|
59
|
+
def self.strikethrough(state, silent)
|
60
|
+
max = state.posMax
|
61
|
+
start = state.pos
|
62
|
+
marker = state.src.charCodeAt(start)
|
63
|
+
|
64
|
+
return false if (marker != 0x7E) # ~
|
65
|
+
return false if (silent) # don't run any pairs in validation mode
|
66
|
+
|
67
|
+
res = scanDelims(state, start)
|
68
|
+
startCount = res[:delims]
|
69
|
+
if (!res[:can_open])
|
70
|
+
state.pos += startCount
|
71
|
+
# Earlier we checked !silent, but this implementation does not need it
|
72
|
+
state.pending += state.src.slice(start...state.pos)
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
|
76
|
+
stack = (startCount / 2).floor
|
77
|
+
return false if (stack <= 0)
|
78
|
+
state.pos = start + startCount
|
79
|
+
|
80
|
+
while (state.pos < max)
|
81
|
+
if (state.src.charCodeAt(state.pos) == marker)
|
82
|
+
res = scanDelims(state, state.pos)
|
83
|
+
count = res[:delims]
|
84
|
+
tagCount = (count / 2).floor
|
85
|
+
if (res[:can_close])
|
86
|
+
if (tagCount >= stack)
|
87
|
+
state.pos += count - 2
|
88
|
+
found = true
|
89
|
+
break
|
90
|
+
end
|
91
|
+
stack -= tagCount
|
92
|
+
state.pos += count
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
stack += tagCount if (res[:can_open])
|
97
|
+
state.pos += count
|
98
|
+
next
|
99
|
+
end
|
100
|
+
|
101
|
+
state.md.inline.skipToken(state)
|
102
|
+
end
|
103
|
+
|
104
|
+
if (!found)
|
105
|
+
# parser failed to find ending tag, so it's not valid emphasis
|
106
|
+
state.pos = start
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
# found!
|
111
|
+
state.posMax = state.pos
|
112
|
+
state.pos = start + 2
|
113
|
+
|
114
|
+
# Earlier we checked !silent, but this implementation does not need it
|
115
|
+
token = state.push('s_open', 's', 1)
|
116
|
+
token.markup = '~~'
|
117
|
+
|
118
|
+
state.md.inline.tokenize(state)
|
119
|
+
|
120
|
+
token = state.push('s_close', 's', -1)
|
121
|
+
token.markup = '~~'
|
122
|
+
|
123
|
+
state.pos = state.posMax + 2
|
124
|
+
state.posMax = max
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Skip text characters for text token, place those to pending buffer
|
2
|
+
# and increment current pos
|
3
|
+
#------------------------------------------------------------------------------
|
4
|
+
module MarkdownIt
|
5
|
+
module RulesInline
|
6
|
+
class Text
|
7
|
+
|
8
|
+
# Rule to skip pure text
|
9
|
+
# '{}$%@~+=:' reserved for extentions
|
10
|
+
|
11
|
+
# !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~
|
12
|
+
|
13
|
+
# !!!! Don't confuse with "Markdown ASCII Punctuation" chars
|
14
|
+
# http://spec.commonmark.org/0.15/#ascii-punctuation-character
|
15
|
+
#------------------------------------------------------------------------------
|
16
|
+
def self.isTerminatorChar(ch)
|
17
|
+
case ch
|
18
|
+
when 0x0A, # \n
|
19
|
+
0x21, # !
|
20
|
+
0x23, # #
|
21
|
+
0x24, # $
|
22
|
+
0x25, # %
|
23
|
+
0x26, # &
|
24
|
+
0x2A, # *
|
25
|
+
0x2B, # +
|
26
|
+
0x2D, # -
|
27
|
+
0x3A, # :
|
28
|
+
0x3C, # <
|
29
|
+
0x3D, # =
|
30
|
+
0x3E, # >
|
31
|
+
0x40, # @
|
32
|
+
0x5B, # [
|
33
|
+
0x5C, # \
|
34
|
+
0x5D, # ]
|
35
|
+
0x5E, # ^
|
36
|
+
0x5F, # _
|
37
|
+
0x60, # `
|
38
|
+
0x7B, # {
|
39
|
+
0x7D, # }
|
40
|
+
0x7E # ~
|
41
|
+
return true
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#------------------------------------------------------------------------------
|
48
|
+
def self.text(state, silent)
|
49
|
+
pos = state.pos
|
50
|
+
|
51
|
+
while pos < state.posMax && !self.isTerminatorChar(state.src.charCodeAt(pos))
|
52
|
+
pos += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
return false if pos == state.pos
|
56
|
+
|
57
|
+
state.pending += state.src.slice(state.pos...pos) if !silent
|
58
|
+
state.pos = pos
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
# // Alternative implementation, for memory.
|
63
|
+
# //
|
64
|
+
# // It costs 10% of performance, but allows extend terminators list, if place it
|
65
|
+
# // to `ParcerInline` property. Probably, will switch to it sometime, such
|
66
|
+
# // flexibility required.
|
67
|
+
#
|
68
|
+
# /*
|
69
|
+
# var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/;
|
70
|
+
#
|
71
|
+
# module.exports = function text(state, silent) {
|
72
|
+
# var pos = state.pos,
|
73
|
+
# idx = state.src.slice(pos).search(TERMINATOR_RE);
|
74
|
+
#
|
75
|
+
# // first char is terminator -> empty text
|
76
|
+
# if (idx === 0) { return false; }
|
77
|
+
#
|
78
|
+
# // no terminator -> text till end of string
|
79
|
+
# if (idx < 0) {
|
80
|
+
# if (!silent) { state.pending += state.src.slice(pos); }
|
81
|
+
# state.pos = state.src.length;
|
82
|
+
# return true;
|
83
|
+
# }
|
84
|
+
#
|
85
|
+
# if (!silent) { state.pending += state.src.slice(pos, pos + idx); }
|
86
|
+
#
|
87
|
+
# state.pos += idx;
|
88
|
+
#
|
89
|
+
# return true;
|
90
|
+
# };*/
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Token class
|
2
|
+
#------------------------------------------------------------------------------
|
3
|
+
module MarkdownIt
|
4
|
+
class Token
|
5
|
+
|
6
|
+
attr_accessor :type, :tag, :attrs, :map, :nesting, :level, :children
|
7
|
+
attr_accessor :content, :markup, :info, :meta, :block, :hidden
|
8
|
+
|
9
|
+
# new Token(type, tag, nesting)
|
10
|
+
#
|
11
|
+
# Create new token and fill passed properties.
|
12
|
+
#------------------------------------------------------------------------------
|
13
|
+
def initialize(type, tag, nesting)
|
14
|
+
# * Token#type -> String
|
15
|
+
# *
|
16
|
+
# * Type of the token (string, e.g. "paragraph_open")
|
17
|
+
@type = type
|
18
|
+
|
19
|
+
# * Token#tag -> String
|
20
|
+
# *
|
21
|
+
# * html tag name, e.g. "p"
|
22
|
+
@tag = tag
|
23
|
+
|
24
|
+
# * Token#attrs -> Array
|
25
|
+
# *
|
26
|
+
# * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]`
|
27
|
+
@attrs = nil
|
28
|
+
|
29
|
+
# * Token#map -> Array
|
30
|
+
# *
|
31
|
+
# * Source map info. Format: `[ line_begin, line_end ]`
|
32
|
+
@map = nil
|
33
|
+
|
34
|
+
# * Token#nesting -> Number
|
35
|
+
# *
|
36
|
+
# * Level change (number in {-1, 0, 1} set), where:
|
37
|
+
# *
|
38
|
+
# * - `1` means the tag is opening
|
39
|
+
# * - `0` means the tag is self-closing
|
40
|
+
# * - `-1` means the tag is closing
|
41
|
+
@nesting = nesting
|
42
|
+
|
43
|
+
# * Token#level -> Number
|
44
|
+
# *
|
45
|
+
# * nesting level, the same as `state.level`
|
46
|
+
@level = 0
|
47
|
+
|
48
|
+
# * Token#children -> Array
|
49
|
+
# *
|
50
|
+
# * An array of child nodes (inline and img tokens)
|
51
|
+
@children = nil
|
52
|
+
|
53
|
+
# * Token#content -> String
|
54
|
+
# *
|
55
|
+
# * In a case of self-closing tag (code, html, fence, etc.),
|
56
|
+
# * it has contents of this tag.
|
57
|
+
@content = ''
|
58
|
+
|
59
|
+
# * Token#markup -> String
|
60
|
+
# *
|
61
|
+
# * '*' or '_' for emphasis, fence string for fence, etc.
|
62
|
+
@markup = ''
|
63
|
+
|
64
|
+
# * Token#info -> String
|
65
|
+
# *
|
66
|
+
# * fence infostring
|
67
|
+
@info = ''
|
68
|
+
|
69
|
+
# * Token#meta -> Object
|
70
|
+
# *
|
71
|
+
# * A place for plugins to store an arbitrary data
|
72
|
+
@meta = nil
|
73
|
+
|
74
|
+
# * Token#block -> Boolean
|
75
|
+
# *
|
76
|
+
# * True for block-level tokens, false for inline tokens.
|
77
|
+
# * Used in renderer to calculate line breaks
|
78
|
+
@block = false
|
79
|
+
|
80
|
+
# * Token#hidden -> Boolean
|
81
|
+
# *
|
82
|
+
# * If it's true, ignore this element when rendering. Used for tight lists
|
83
|
+
# * to hide paragraphs.
|
84
|
+
@hidden = false
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# * Token.attrIndex(name) -> Number
|
89
|
+
# *
|
90
|
+
# * Search attribute index by name.
|
91
|
+
#------------------------------------------------------------------------------
|
92
|
+
def attrIndex(name)
|
93
|
+
return -1 if !@attrs
|
94
|
+
|
95
|
+
attrs = @attrs
|
96
|
+
|
97
|
+
attrs.each_with_index do |attr_, index|
|
98
|
+
return index if attr_[0] == name
|
99
|
+
end
|
100
|
+
return -1
|
101
|
+
end
|
102
|
+
|
103
|
+
# * Token.attrPush(attrData)
|
104
|
+
# *
|
105
|
+
# * Add `[ name, value ]` attribute to list. Init attrs if necessary
|
106
|
+
#------------------------------------------------------------------------------
|
107
|
+
def attrPush(attrData)
|
108
|
+
if @attrs
|
109
|
+
@attrs.push(attrData)
|
110
|
+
else
|
111
|
+
@attrs = [ attrData ]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
#------------------------------------------------------------------------------
|
116
|
+
def to_json
|
117
|
+
{
|
118
|
+
type: @type,
|
119
|
+
tag: @tag,
|
120
|
+
attrs: @attrs,
|
121
|
+
map: @map,
|
122
|
+
nesting: @nesting,
|
123
|
+
level: @level,
|
124
|
+
children: @children.nil? ? nil : @children.each {|t| t.to_json},
|
125
|
+
content: @content,
|
126
|
+
markup: @markup,
|
127
|
+
info: @info,
|
128
|
+
meta: @meta,
|
129
|
+
block: @block,
|
130
|
+
hidden: @hidden
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|