minidown 2.1.1

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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +73 -0
  7. data/Rakefile +4 -0
  8. data/bin/minidown +33 -0
  9. data/lib/minidown.rb +13 -0
  10. data/lib/minidown/document.rb +142 -0
  11. data/lib/minidown/element.rb +40 -0
  12. data/lib/minidown/elements.rb +15 -0
  13. data/lib/minidown/elements/block_element.rb +37 -0
  14. data/lib/minidown/elements/code_block_element.rb +44 -0
  15. data/lib/minidown/elements/dividing_line_element.rb +11 -0
  16. data/lib/minidown/elements/html_element.rb +22 -0
  17. data/lib/minidown/elements/indent_code_element.rb +23 -0
  18. data/lib/minidown/elements/line_element.rb +28 -0
  19. data/lib/minidown/elements/list_element.rb +36 -0
  20. data/lib/minidown/elements/list_group_element.rb +87 -0
  21. data/lib/minidown/elements/order_list_element.rb +20 -0
  22. data/lib/minidown/elements/paragraph_element.rb +56 -0
  23. data/lib/minidown/elements/raw_html_element.rb +11 -0
  24. data/lib/minidown/elements/table_element.rb +80 -0
  25. data/lib/minidown/elements/text_element.rb +198 -0
  26. data/lib/minidown/elements/unorder_list_element.rb +31 -0
  27. data/lib/minidown/html_helper.rb +18 -0
  28. data/lib/minidown/parser.rb +19 -0
  29. data/lib/minidown/utils.rb +28 -0
  30. data/lib/minidown/version.rb +3 -0
  31. data/minidown.gemspec +26 -0
  32. data/spec/blank_line_spec.rb +21 -0
  33. data/spec/code_block_spec.rb +182 -0
  34. data/spec/dividing_line_spec.rb +35 -0
  35. data/spec/h1_and_h2_spec.rb +55 -0
  36. data/spec/indent_block_spec.rb +17 -0
  37. data/spec/li_spec.rb +354 -0
  38. data/spec/minidown_spec.rb +12 -0
  39. data/spec/paragraph_spec.rb +17 -0
  40. data/spec/raw_html_spec.rb +43 -0
  41. data/spec/spec_helper.rb +2 -0
  42. data/spec/start_with_gt_spec.rb +70 -0
  43. data/spec/start_with_shape_spec.rb +42 -0
  44. data/spec/table_spec.rb +63 -0
  45. data/spec/task_list_spec.rb +18 -0
  46. data/spec/text_element_spec.rb +317 -0
  47. metadata +175 -0
@@ -0,0 +1,11 @@
1
+ module Minidown
2
+ class DividingLineElement < Element
3
+ def parse
4
+ nodes << self
5
+ end
6
+
7
+ def to_html
8
+ '<hr>'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Minidown
2
+ class HtmlElement < Element
3
+ attr_reader :name
4
+
5
+ def initialize doc, content, name
6
+ super doc, content
7
+ @name = name
8
+ end
9
+
10
+ def parse
11
+ nodes << self
12
+ end
13
+
14
+ def to_html
15
+ build_tag @name do |tag|
16
+ # self.content is some Element
17
+ self.content = content.text if ParagraphElement === self.content
18
+ tag << self.content.to_html
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Minidown
2
+ class IndentCodeElement < Element
3
+ def initialize *_
4
+ super
5
+ @lines = [content]
6
+ end
7
+
8
+ def parse
9
+ while line = unparsed_lines.shift
10
+ case line
11
+ when Utils::Regexp[:indent_code]
12
+ @lines << $1
13
+ else
14
+ unparsed_lines.unshift line
15
+ break
16
+ end
17
+ end
18
+ unparsed_lines.unshift '```'
19
+ unparsed_lines.unshift *@lines
20
+ unparsed_lines.unshift '```'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module Minidown
2
+ class LineElement < Element
3
+ attr_accessor :display
4
+
5
+ def initialize doc, content=nil
6
+ super
7
+ @display = true
8
+ end
9
+
10
+ def blank?
11
+ true
12
+ end
13
+
14
+ def parse
15
+ node = nodes.last
16
+ @display = (doc.within_block || TextElement === node)
17
+ nodes << self
18
+ end
19
+
20
+ def to_html
21
+ if @display
22
+ br_tag
23
+ else
24
+ ''
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ module Minidown
2
+ class ListElement < Element
3
+ attr_accessor :p_tag_content, :contents, :task_list, :checked
4
+ CheckedBox = '<input type="checkbox" class="task-list-item-checkbox" checked="" disabled="">'.freeze
5
+ UnCheckedBox = '<input type="checkbox" class="task-list-item-checkbox" disabled="">'.freeze
6
+
7
+ def initialize *_
8
+ super
9
+ @p_tag_content = false
10
+ @contents = [TextElement.new(doc, content)]
11
+ @task_list = false
12
+ @checked = false
13
+ end
14
+
15
+ def to_html
16
+ @contents.map! do |content|
17
+ ParagraphElement === content ? content.text : content
18
+ end if @p_tag_content
19
+ content = list_content
20
+ content = build_tag('p'.freeze){|p| p << content} if @p_tag_content
21
+ attr = nil
22
+ attr = {class: 'task-list-item'.freeze} if @task_list
23
+ build_tag 'li'.freeze, attr do |li|
24
+ li << content
25
+ end
26
+ end
27
+
28
+ def list_content
29
+ content = @contents.map(&:to_html).join(@p_tag_content ? br_tag : "\n".freeze)
30
+ if @task_list
31
+ content = (@checked ? CheckedBox : UnCheckedBox) + content
32
+ end
33
+ content
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,87 @@
1
+ module Minidown
2
+ class ListGroupElement < Element
3
+ IndentRegexp = /\A\s{4,}(.+)/
4
+ StartWithBlankRegexp = /\A\s+(.+)/
5
+
6
+ attr_accessor :lists, :indent_level
7
+
8
+ def parse
9
+ nodes << self
10
+ while line = unparsed_lines.shift
11
+ #handle nested list
12
+ if (line =~ UnorderListElement::NestRegexp && list_class = UnorderListElement) || (line =~ OrderListElement::NestRegexp && list_class = OrderListElement)
13
+ li, str = $1.size, $2
14
+ if li > @indent_level
15
+ list_class.new(doc, str, li).parse
16
+ @lists.last.contents << nodes.pop
17
+ next
18
+ elsif li == @indent_level
19
+ list_class.new(doc, str, li).parse
20
+ child = nodes.pop
21
+ if LineElement === nodes.last
22
+ @lists.last.p_tag_content = child.lists.first.p_tag_content = true
23
+ end
24
+ if child.is_a?(ListGroupElement)
25
+ nodes.push *child.children
26
+ @lists.push *child.lists
27
+ else
28
+ @lists.last.contents << child
29
+ end
30
+ next
31
+ else
32
+ unparsed_lines.unshift line
33
+ break
34
+ end
35
+ end
36
+
37
+ doc.parse_line line
38
+ child = nodes.pop
39
+ case child
40
+ when self.class
41
+ if LineElement === nodes.last
42
+ @lists.last.p_tag_content = child.lists.first.p_tag_content = true
43
+ end
44
+ nodes.push *child.children
45
+ @lists.push *child.lists
46
+ break
47
+ when ParagraphElement
48
+ contents = @lists.last.contents
49
+ if line =~ StartWithBlankRegexp
50
+ doc.parse_line $1
51
+ node = nodes.pop
52
+ if TextElement === node || ParagraphElement === node
53
+ if TextElement === contents.last
54
+ contents.push(contents.pop.paragraph)
55
+ end
56
+ node = node.paragraph if TextElement === node
57
+ end
58
+ else
59
+ if @blank
60
+ unparsed_lines.unshift line
61
+ break
62
+ end
63
+ node = child.text
64
+ end
65
+ contents << node if node
66
+ when LineElement
67
+ next_line = unparsed_lines.first
68
+ if next_line.nil? || next_line.empty? || StartWithBlankRegexp === next_line || self.class.const_get(:ListRegexp) === next_line
69
+ child.display = false
70
+ nodes << child
71
+ else
72
+ unparsed_lines.unshift line
73
+ break
74
+ end
75
+ else
76
+ @put_back << child if child
77
+ break
78
+ end
79
+ @blank = (LineElement === child)
80
+ end
81
+ children_range = (nodes.index(self) + 1)..-1
82
+ children.push *nodes[children_range]
83
+ nodes[children_range] = []
84
+ nodes.push *@put_back
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,20 @@
1
+ module Minidown
2
+ class OrderListElement < ListGroupElement
3
+ NestRegexp = /\A(\s*)\d+\.\s+(.+)/
4
+ ListRegexp = Minidown::Utils::Regexp[:order_list]
5
+
6
+ def initialize doc, line, indent_level = 0
7
+ super doc, line
8
+ @children << ListElement.new(doc, content)
9
+ @lists = @children.dup
10
+ @indent_level = indent_level
11
+ @put_back = []
12
+ end
13
+
14
+ def to_html
15
+ build_tag 'ol'.freeze do |content|
16
+ children.each { |child| content << child.to_html}
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ module Minidown
2
+ class ParagraphElement < Element
3
+ attr_reader :contents
4
+ attr_accessor :extra
5
+
6
+ ExcludeSchemeRegexp = /\A[^@:]+\z/
7
+ StringSymbolRegexp = /"|'/
8
+
9
+ def initialize *_
10
+ super
11
+ @contents = [raw_content]
12
+ @extra = false
13
+ end
14
+
15
+ def parse
16
+ if ParagraphElement === nodes.last
17
+ nodes.last.contents << raw_content
18
+ else
19
+ nodes << self
20
+ end
21
+ end
22
+
23
+ def text
24
+ build_element raw_content
25
+ end
26
+
27
+ def to_html
28
+ if @extra
29
+ contents.map{|content| ParagraphElement.new(doc, content).to_html }.join ''.freeze
30
+ else
31
+ contents.map! do |line|
32
+ build_element line
33
+ end
34
+ build_tag 'p'.freeze do |content|
35
+ pre_elem = contents.shift
36
+ content << pre_elem.to_html
37
+ while elem = contents.shift
38
+ content << br_tag if TextElement === pre_elem && TextElement === elem
39
+ content << elem.to_html
40
+ pre_elem = elem
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def build_element content_str
49
+ if Utils::Regexp[:raw_html] =~ content_str && (raw = $1) && (ExcludeSchemeRegexp =~ raw || StringSymbolRegexp =~ raw)
50
+ RawHtmlElement.new doc, raw
51
+ else
52
+ TextElement.new doc, content_str
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ module Minidown
2
+ class RawHtmlElement < Element
3
+ def parse
4
+ nodes << self
5
+ end
6
+
7
+ def to_html
8
+ content
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,80 @@
1
+ module Minidown
2
+ class TableElement < Element
3
+ AlignSpecRegexp = /\A\|?\s*([\-:]\-+[\-:]\s*(?:\|\s*[\-:]\-+[\-:]\s*)*)\|?\s*\z/
4
+
5
+ def initialize doc, content, raw_head
6
+ super doc, content
7
+ @raw_head = raw_head
8
+ @heads = @raw_head.split('|'.freeze).map! &:strip
9
+ @column_count = @heads.count
10
+ end
11
+
12
+ def check_column_spec raw_column_spec
13
+ if @valid.nil?
14
+ @valid = Utils::Regexp[:pipe_symbol] =~ raw_column_spec && AlignSpecRegexp =~ raw_column_spec && (column_spec_str = $1) && (@column_specs = column_spec_str.split('|'.freeze).map! &:strip) && @column_specs.count == @column_count
15
+ else
16
+ @valid
17
+ end
18
+ end
19
+
20
+ def parse
21
+ if @valid
22
+ nodes << self
23
+ @bodys = []
24
+ @column_specs.map! do |column_spec|
25
+ if column_spec[0] == column_spec[-1]
26
+ column_spec[0] == ':'.freeze ? 'center'.freeze : nil
27
+ else
28
+ column_spec[0] == ':'.freeze ? 'left'.freeze : 'right'.freeze
29
+ end
30
+ end
31
+
32
+ while line = unparsed_lines.shift
33
+ if Utils::Regexp[:table] =~ line && (cells = $1.split('|'.freeze).map! &:strip) && @column_count == cells.count
34
+ @bodys << cells
35
+ else
36
+ unparsed_lines.unshift line
37
+ break
38
+ end
39
+ end
40
+ else
41
+ raise 'table column specs not valid'
42
+ end
43
+ end
44
+
45
+ def to_html
46
+ attrs = @column_specs.map do |align|
47
+ {align: align}.freeze if align
48
+ end
49
+ build_tag 'table'.freeze do |table|
50
+ thead = build_tag 'thead'.freeze do |thead|
51
+ tr = build_tag 'tr'.freeze do |tr|
52
+ @heads.each_with_index do |cell, i|
53
+ th = build_tag 'th'.freeze, attrs[i] do |th|
54
+ th << cell
55
+ end
56
+ tr << th
57
+ end
58
+ end
59
+ thead << tr
60
+ end
61
+ table << thead
62
+
63
+ tbody = build_tag 'tbody'.freeze do |tbody|
64
+ @bodys.each do |row|
65
+ tr = build_tag 'tr'.freeze do |tr|
66
+ row.each_with_index do |cell, i|
67
+ td = build_tag 'td'.freeze, attrs[i] do |td|
68
+ td << TextElement.new(doc, cell).to_html
69
+ end
70
+ tr << td
71
+ end
72
+ end
73
+ tbody << tr
74
+ end
75
+ end
76
+ table << tbody
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,198 @@
1
+ module Minidown
2
+ class TextElement < Element
3
+ EscapeChars = %w{# &gt; * + \- ` _ { } ( ) . ! \[ \] ~}
4
+ EscapeRegexp = /(?<!\\)\\([#{EscapeChars.join '|'}|\\])/
5
+
6
+ Regexp = {
7
+ tag: /(\\*)&lt;(.+?)(\\*)&gt;/,
8
+ quot: /&quot;/,
9
+ link: /(?<!\!)\[(.+?)\]\((.+?)\)/,
10
+ link_title: /((?<=").+?(?="))/,
11
+ link_url: /(\S+)/,
12
+ link_ref: /(?<!\!)\[(.+?)\]\s*\[(.*?)\]/,
13
+ image: /\!\[(.+?)\]\((.+?)\)/,
14
+ image_ref: /\!\[(.+?)\]\s*\[(.*?)\]/,
15
+ star: /((?<!\\)\*{1,2})(.+?)\1/,
16
+ underline: /(?<=\A|\s)((?<!\\)\_{1,2})(\S+)\1(?=\z|\s)/,
17
+ delete_line: /(?<!\\)~~(?!\s)(.+?)(?<!\s)~~/,
18
+ quotlink: /\<(.+?)\>/,
19
+ link_scheme: /\A\S+?\:\/\//,
20
+ email: /\A[A-Za-z0-9]+@[A-Za-z0-9]+\.[A-Za-z0-9]+/,
21
+ auto_email: /(?<!\S)[A-Za-z0-9]+@[A-Za-z0-9]+\.[A-Za-z0-9]+(?!\S)/,
22
+ auto_link: /(?<!\S)\w+?\:\/\/.+?(?!\S)/,
23
+ inline_code: /(?<!\\)(`+)\s*(.+?)\s*(?<!\\)\1/
24
+ }.freeze
25
+
26
+ attr_accessor :escape, :convert, :sanitize
27
+
28
+ def initialize *_
29
+ super
30
+ @escape = true
31
+ @sanitize = false
32
+ @convert = true
33
+ end
34
+
35
+ def parse
36
+ nodes << self
37
+ end
38
+
39
+ def content
40
+ str = super
41
+ str = convert_str(str) if convert
42
+ escape_content! str
43
+ escape_str! str
44
+ escape_html str if sanitize
45
+ str
46
+ end
47
+
48
+ def escape_str! str
49
+ str.gsub!(EscapeRegexp, '\\1'.freeze) if escape
50
+ end
51
+
52
+ def escape_html str
53
+ str.replace Utils.escape_html(str)
54
+ end
55
+
56
+ def escape_content! str
57
+ return str unless @escape
58
+ escape_html str
59
+
60
+ str.gsub! Regexp[:tag] do
61
+ left, tag, right = $1, $2, $3
62
+ tag.gsub! Regexp[:quot] do
63
+ '"'.freeze
64
+ end
65
+
66
+ left = left.size.odd? ? "#{left[0..-2]}&lt;" : "#{left}<" if left
67
+ left ||= "<".freeze
68
+
69
+ right = right.size.odd? ? "#{right[0..-2]}&gt;" : "#{right}>" if right
70
+ right ||= ">".freeze
71
+
72
+ "#{left}#{tag}#{right}"
73
+ end
74
+ str
75
+ end
76
+
77
+ def convert_str str
78
+ #auto link
79
+ str.gsub! Regexp[:auto_link] do |origin_str|
80
+ build_tag 'a'.freeze, href: origin_str do |a|
81
+ a << origin_str
82
+ end
83
+ end
84
+
85
+ #auto email
86
+ str.gsub! Regexp[:auto_email] do |origin_str|
87
+ build_tag 'a'.freeze, href: "mailto:#{origin_str}" do |a|
88
+ a << origin_str
89
+ end
90
+ end
91
+
92
+ #parse <link>
93
+ str.gsub! Regexp[:quotlink] do |origin_str|
94
+ link = $1
95
+ attr = case link
96
+ when Regexp[:link_scheme]
97
+ {href: link}
98
+ when Regexp[:email]
99
+ {href: "mailto:#{link}"}
100
+ end
101
+ attr ? build_tag('a'.freeze, attr){|a| a << link} : origin_str
102
+ end
103
+
104
+ #parse * _
105
+ Regexp.values_at(:star, :underline).each do |regex|
106
+ str.gsub! regex do |origin_str|
107
+ tag_name = $1.size > 1 ? 'strong'.freeze : 'em'.freeze
108
+ build_tag tag_name do |tag|
109
+ tag << $2
110
+ end
111
+ end
112
+ end
113
+
114
+ #parse ~~del~~
115
+ str.gsub! Regexp[:delete_line] do |origin_str|
116
+ build_tag 'del'.freeze do |tag|
117
+ tag << $1
118
+ end
119
+ end
120
+
121
+ #convert image reference
122
+ str.gsub! Regexp[:image_ref] do |origin_str|
123
+ alt = $1
124
+ id = ($2 && !$2.empty?) ? $2 : $1
125
+ ref = doc.links_ref[id.downcase]
126
+ if ref
127
+ attr = {src: ref[:url], alt: alt}
128
+ attr[:title] = ref[:title] if ref[:title] && !ref[:title].empty?
129
+ build_tag 'img'.freeze, attr
130
+ else
131
+ origin_str
132
+ end
133
+ end
134
+
135
+ #convert image syntax
136
+ str.gsub! Regexp[:image] do
137
+ alt, url = $1, $2
138
+ url =~ Regexp[:link_title]
139
+ title = $1
140
+ url =~ Regexp[:link_url]
141
+ url = $1
142
+ attr = {src: url, alt: alt}
143
+ attr[:title] = title if title
144
+ build_tag 'img'.freeze, attr
145
+ end
146
+
147
+ #convert link reference
148
+ str.gsub! Regexp[:link_ref] do |origin_str|
149
+ text = $1
150
+ id = ($2 && !$2.empty?) ? $2 : $1
151
+ ref = doc.links_ref[id.downcase]
152
+ if ref
153
+ attr = {href: ref[:url]}
154
+ attr[:title] = ref[:title] if ref[:title] && !ref[:title].empty?
155
+ build_tag 'a'.freeze, attr do |a|
156
+ a << text
157
+ end
158
+ else
159
+ origin_str
160
+ end
161
+ end
162
+
163
+ #convert link syntax
164
+ str.gsub! Regexp[:link] do
165
+ text, url = $1, $2
166
+ url =~ Regexp[:link_title]
167
+ title = $1
168
+ url =~ Regexp[:link_url]
169
+ url = $1
170
+ attr = {href: url}
171
+ attr[:title] = title if title
172
+ build_tag 'a'.freeze, attr do |content|
173
+ content << text
174
+ end
175
+ end
176
+
177
+ escape_content! str
178
+
179
+ #inline code
180
+ str.gsub! Regexp[:inline_code] do |origin_str|
181
+ build_tag 'code'.freeze do |code|
182
+ code << escape_html($2)
183
+ end
184
+ end
185
+ escape_str! str
186
+ @escape = false
187
+ str
188
+ end
189
+
190
+ def paragraph
191
+ ParagraphElement.new doc, raw_content
192
+ end
193
+
194
+ def to_html
195
+ content
196
+ end
197
+ end
198
+ end