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