rsyntaxtree 0.8.8 → 1.0.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.
@@ -6,15 +6,58 @@
6
6
  #==========================
7
7
  #
8
8
  # Image utility functions to inspect text font metrics
9
- #
10
- # This file is part of RSyntaxTree, which is a ruby port of Andre Eisenbach's
11
- # excellent program phpSyntaxTree.
12
- #
13
9
  # Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
14
- # Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
10
+
11
+ require 'rmagick'
12
+ include Magick
15
13
 
16
14
  class String
17
15
  def contains_cjk?
18
- !!(self =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/)
16
+ !!(self.gsub(WHITESPACE_BLOCK, "") =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}|[^\x01-\x7E]/)
17
+ end
18
+
19
+ def contains_emoji?
20
+ !!(self.gsub(WHITESPACE_BLOCK, "").gsub(/\d/, "") =~ /\p{Emoji}/)
21
+ end
22
+
23
+ def all_emoji?
24
+ !!(self.gsub(WHITESPACE_BLOCK, "").gsub(/\d/, "") =~ /\A\p{Emoji}[\p{Emoji}\s]*\z/)
19
25
  end
26
+
27
+ def split_by_emoji
28
+ results = []
29
+ self.split(//).each do |ch|
30
+ case ch
31
+ when /\d/, WHITESPACE_BLOCK
32
+ results << {:type => :normal, :char => ch}
33
+ when /\p{Emoji}/
34
+ results << {:type => :emoji, :char => ch}
35
+ else
36
+ results << {:type => :normal, :char => ch}
37
+ end
38
+ end
39
+ results.reject{|string| string == ""}
40
+ end
41
+ end
42
+
43
+ module FontMetrics
44
+ def get_metrics(text, font, fontsize, font_style, font_weight)
45
+ background = Image.new(1, 1)
46
+ gc = Draw.new
47
+ gc.annotate(background, 0, 0, 0, 0, text) do |gc|
48
+ gc.font = font
49
+ gc.font_style = font_style == :italic ? ItalicStyle : NormalStyle
50
+ gc.font_weight = font_weight == :bold ? BoldWeight : NormalWeight
51
+ gc.pointsize = fontsize
52
+ gc.gravity = CenterGravity
53
+ gc.stroke = 'none'
54
+ gc.kerning = 0
55
+ gc.interline_spacing = 0
56
+ gc.interword_spacing = 0
57
+ end
58
+ metrics = gc.get_multiline_type_metrics(background, text)
59
+ return metrics
60
+ end
61
+ module_function :get_metrics
20
62
  end
63
+
@@ -1,4 +1,4 @@
1
1
  module RSyntaxTree
2
- VERSION = "0.8.8"
2
+ VERSION = "1.0.1"
3
3
  end
4
4
 
data/lib/rsyntaxtree.rb CHANGED
@@ -7,179 +7,168 @@
7
7
  #
8
8
  # Facade of rsyntaxtree library. When loaded by a driver script, it does all
9
9
  # the necessary 'require' to use the library.
10
- #
11
- # This file is part of RSyntaxTree, which is originally a ruby port of Andre Eisenbach's
12
- # excellent program phpSyntaxTree.
13
10
  # Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
14
- # Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
15
11
 
16
12
  $LOAD_PATH << File.join( File.dirname(__FILE__), 'rsyntaxtree')
17
13
 
14
+ FONT_DIR = File.expand_path(File.dirname(__FILE__) + "/../fonts")
15
+ ETYPE_NODE = 1
16
+ ETYPE_LEAF = 2
17
+ SUBSCRIPT_CONST = 0.7
18
+ FONT_SCALING = 2
19
+ WHITESPACE_BLOCK = "■"
20
+
21
+ class RSTError < StandardError
22
+ def initialize(msg="Error: something unexpected occurred")
23
+ msg.gsub!(WHITESPACE_BLOCK, "<>")
24
+ super msg
25
+ end
26
+ end
27
+
18
28
  require 'cgi'
29
+ require 'rsvg2'
19
30
  require 'utils'
20
31
  require 'element'
21
32
  require 'elementlist'
22
33
  require 'string_parser'
23
- require 'tree_graph'
24
34
  require 'svg_graph'
25
- require 'error_message'
26
35
  require 'version'
36
+ require 'rmagick'
37
+
38
+ module RSyntaxTree
39
+
40
+ class RSGenerator
41
+
42
+ def initialize(params = {})
43
+ new_params = {}
44
+ fontset = {}
45
+ params.each do |keystr, value|
46
+ key = keystr.to_sym
47
+ case key
48
+ when :data
49
+ data = value
50
+ data = data.gsub('-AMP-', '&')
51
+ .gsub('-PERCENT-', "%")
52
+ .gsub('-PRIME-', "'")
53
+ .gsub('-SCOLON-', ';')
54
+ .gsub('-OABRACKET-', '<')
55
+ .gsub('-CABRACKET-', '>')
56
+ new_params[key] = data
57
+
58
+ when :symmetrize, :color, :transparent
59
+ new_params[key] = value && (value != "off" && value != "false") ? true : false
60
+ when :fontsize
61
+ new_params[key] = value.to_i * FONT_SCALING
62
+ when :margin
63
+ new_params[key] = value.to_i * FONT_SCALING * 5
64
+ when :vheight
65
+ new_params[key] = value.to_f
66
+ when :fontstyle
67
+ if value == "noto-sans" || value == "sans"
68
+ fontset[:normal] = FONT_DIR + "/NotoSans-Regular.ttf"
69
+ fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf"
70
+ fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf"
71
+ fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
72
+ fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
73
+ fontset[:cjk] = FONT_DIR + "/NotoSansJP-Regular.otf"
74
+ fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
75
+ new_params[:fontstyle] = "sans"
76
+ elsif value == "noto-serif" || value == "serif"
77
+ fontset[:normal] = FONT_DIR + "/NotoSerif-Regular.ttf"
78
+ fontset[:italic] = FONT_DIR + "/NotoSerif-Italic.ttf"
79
+ fontset[:bold] = FONT_DIR + "/NotoSerif-Bold.ttf"
80
+ fontset[:bolditalic] = FONT_DIR + "/NotoSerif-BoldItalic.ttf"
81
+ fontset[:math] = FONT_DIR + "/latinmodern-math.otf"
82
+ fontset[:cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
83
+ fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
84
+ new_params[:fontstyle] = "serif"
85
+ elsif value == "cjk zenhei" || value == "cjk"
86
+ fontset[:normal] = FONT_DIR + "/wqy-zenhei.ttf"
87
+ fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf"
88
+ fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf"
89
+ fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
90
+ fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
91
+ fontset[:cjk] = FONT_DIR + "/wqy-zenhei.ttf"
92
+ fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
93
+ new_params[:fontstyle] = "cjk"
94
+ end
95
+ else
96
+ new_params[key] = value
97
+ end
98
+ end
27
99
 
28
- FONT_DIR = File.expand_path(File.dirname(__FILE__) + "/../fonts")
100
+ # defaults to the following
101
+ @params = {
102
+ :symmetrize => true,
103
+ :color => true,
104
+ :transparent => false,
105
+ :fontsize => 16,
106
+ :format => "png",
107
+ :leafstyle => "auto",
108
+ :filename => "syntree",
109
+ :data => "",
110
+ :margin => 0,
111
+ :vheight => 1.0,
112
+ }
113
+
114
+ @params.merge! new_params
115
+ @params[:fontsize] = @params[:fontsize] * FONT_SCALING
116
+ @params[:margin] = @params[:margin] * FONT_SCALING
117
+ @params[:fontset] = fontset
118
+
119
+ $single_X_metrics = FontMetrics.get_metrics("X", fontset[:normal], @params[:fontsize], :normal, :normal)
120
+ $height_connector_to_text = $single_X_metrics.height / 2.0
121
+ $single_line_height = $single_X_metrics.height * 2.0
122
+ $width_half_X = $single_X_metrics.width / 2.0
123
+ $height_connector = $single_X_metrics.height * 1.0 * @params[:vheight]
124
+ $h_gap_between_nodes = $single_X_metrics.width * 0.8
125
+ $box_vertical_margin = $single_X_metrics.height * 0.8
126
+ end
29
127
 
30
- ETYPE_UNDEFINED = 0
31
- ETYPE_NODE = 1
32
- ETYPE_LEAF = 2
33
- SUBSCRIPT_CONST = 0.7
34
- FONT_SCALING = 2
128
+ def self.check_data(text)
129
+ if text.to_s == ""
130
+ raise RSTError, "Error: input text is empty"
131
+ end
132
+ StringParser.valid?(text)
133
+ end
35
134
 
36
- class RSGenerator
37
- def initialize(params = {})
38
- new_params = {}
39
- params.each do |keystr, value|
40
- key = keystr.to_sym
41
- case key
42
- when :data
43
- data = CGI.unescape(value)
44
- data = data.gsub('-AMP-', '&')
45
- .gsub('-PERCENT-', "%")
46
- .gsub('-PRIME-', "'")
47
- .gsub('-SCOLON-', ';')
48
- .gsub('-OABRACKET-', '<')
49
- .gsub('-CABRACKET-', '>')
50
- new_params[key] = data
51
- new_params[:multibyte] = data.contains_cjk?
52
- when :symmetrize, :color, :autosub, :transparent
53
- new_params[key] = value && value != "off" ? true : false
54
- when :fontsize
55
- new_params[key] = value.to_i * FONT_SCALING
56
- when :margin
57
- new_params[key] = value.to_i * FONT_SCALING * 4
58
- when :vheight
59
- new_params[key] = value.to_f
60
- when :fontstyle
61
- if value == "noto-sans" || value == "sans"
62
- new_params[:font] = FONT_DIR + "/NotoSans-Regular.ttf"
63
- new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
64
- new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
65
- new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
66
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
67
- new_params[:font_cjk] = FONT_DIR + "/NotoSansCJKjp-Regular.otf"
68
- new_params[:fontstyle] = "sans"
69
- elsif value == "noto-serif" || value == "serif"
70
- new_params[:font] = FONT_DIR + "/NotoSerif-Regular.ttf"
71
- new_params[:font_it] = FONT_DIR + "/NotoSerif-Italic.ttf"
72
- new_params[:font_bd] = FONT_DIR + "/NotoSerif-Bold.ttf"
73
- new_params[:font_bdit] = FONT_DIR + "/NotoSerif-BoldItalic.ttf"
74
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
75
- new_params[:font_cjk] = FONT_DIR + "/NotoSerifCJKjp-Regular.otf"
76
- new_params[:fontstyle] = "serif"
77
- elsif value == "cjk zenhei" || value == "cjk"
78
- new_params[:font] = FONT_DIR + "/wqy-zenhei.ttf"
79
- new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
80
- new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
81
- new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
82
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
83
- new_params[:font_cjk] = FONT_DIR + "/wqy-zenhei.ttf"
84
- new_params[:fontstyle] = "sans"
85
- elsif value == "modern-math" || value == "math"
86
- new_params[:font] = FONT_DIR + "/latinmodern-math.otf"
87
- new_params[:font_it] = FONT_DIR + "/lmroman10-italic.otf"
88
- new_params[:font_bd] = FONT_DIR + "/lmroman10-bold.otf"
89
- new_params[:font_bdit] = FONT_DIR + "/lmroman10-bolditalic.otf"
90
- new_params[:font_math] = FONT_DIR + "/latinmodern-math.otf"
91
- new_params[:font_cjk] = FONT_DIR + "/NotoSerifCJKjp-Regular.otf"
92
- new_params[:fontstyle] = "math"
93
- end
135
+ def draw_png(binary = false)
136
+ svg = draw_svg
137
+ rsvg = RSVG::Handle.new_from_data(svg)
138
+ dim = rsvg.dimensions
139
+ surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, dim.width, dim.height)
140
+ context = Cairo::Context.new(surface)
141
+ context.render_rsvg_handle(rsvg)
142
+ b = StringIO.new
143
+ surface.write_to_png(b)
144
+ if binary
145
+ return b
94
146
  else
95
- new_params[key] = value
147
+ return b.string
96
148
  end
97
149
  end
98
150
 
99
- # defaults to the following
100
- @params = {
101
- :symmetrize => true,
102
- :color => true,
103
- :autosub => false,
104
- :transparent => false,
105
- :fontsize => 18,
106
- :format => "png",
107
- :leafstyle => "auto",
108
- :filename => "syntree",
109
- :data => "",
110
- :margin => 0,
111
- :vheight => 1.0,
112
- :fontstyle => "sans",
113
- :font => "/NotoSansCJKjp-Regular.otf",
114
- :font_it => "/NotoSans-Italic.ttf",
115
- :font_bd => "/NotoSans-Bold.ttf",
116
- :font_bdit => "/NotoSans-BoldItalic.ttf",
117
- :font_math => "/NotoSansMath-Regular.ttf"
118
- }
119
- @metrics = {
120
- :e_width => 120,
121
- :e_padd => 14,
122
- :v_space => 20,
123
- :h_space => 20,
124
- :b_side => 10,
125
- :b_topbot => 10
126
- }
127
-
128
- @params.merge! new_params
129
- @params[:fontsize] = @params[:fontsize] * FONT_SCALING
130
- @params[:margin] = @params[:margin] * FONT_SCALING
131
- @metrics[:v_space] = @metrics[:v_space] * @params[:vheight]
132
- end
133
-
134
- def self.check_data(text)
135
- sp = StringParser.new(text)
136
- sp.valid?
137
- end
138
-
139
- def draw_png
140
- @params[:format] = "png"
141
- draw_tree
142
- end
143
-
144
- def draw_jpg
145
- @params[:format] = "jpg"
146
- draw_tree
147
- end
148
-
149
- def draw_gif
150
- @params[:format] = "gif"
151
- draw_tree
152
- end
153
-
154
- def draw_pdf
155
- @params[:format] = "pdf"
156
- draw_tree
157
- end
158
-
159
- def draw_svg
160
- @params[:format] = "svg"
161
- sp = StringParser.new(@params[:data].gsub('&', '&amp;').gsub('%', '&#37;'))
162
- sp.parse
163
- sp.auto_subscript if @params[:autosub]
164
- elist = sp.get_elementlist
165
- graph = SVGGraph.new(elist, @metrics,
166
- @params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
167
- @params[:fontstyle], @params[:font], @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
168
- )
169
- graph.svg_data
170
- end
151
+ def draw_svg
152
+ sp = StringParser.new(@params[:data].gsub('&', '&amp;').gsub('%', '&#37;'), @params[:fontset], @params[:fontsize])
153
+ sp.parse
154
+ graph = SVGGraph.new(sp.get_elementlist, @params)
155
+ graph.svg_data
156
+ end
171
157
 
172
- def draw_tree
173
- sp = StringParser.new(@params[:data])
174
- sp.parse
175
- sp.auto_subscript if @params[:autosub]
176
- elist = sp.get_elementlist
177
- graph = TreeGraph.new(elist, @metrics,
178
- @params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
179
- @params[:fontstyle], @params[:font], @params[:font_it], @params[:font_bd], @params[:font_bdit], @params[:font_math],
180
- @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
181
- )
182
- graph.to_blob(@params[:format])
158
+ def draw_tree
159
+ svg = draw_svg
160
+ image, data = Magick::Image.from_blob(svg) do |im|
161
+ im.format = 'svg'
162
+ end
163
+ if @params[:format] == "png"
164
+ image = draw_png(false)
165
+ else
166
+ image.to_blob do |im|
167
+ im.format = @params[:format].upcase
168
+ end
169
+ end
170
+ image
171
+ end
183
172
  end
184
173
  end
185
174
 
data/rsyntaxtree.gemspec CHANGED
@@ -19,4 +19,6 @@ Gem::Specification.new do |s|
19
19
  # specify any dependencies here; for example:
20
20
  s.add_runtime_dependency 'rmagick', '~> 4.2', '>= 4.2.3'
21
21
  s.add_runtime_dependency 'optimist', '~> 3.0', '>= 3.0.1'
22
+ s.add_runtime_dependency 'parslet'
23
+ s.add_runtime_dependency 'rsvg2'
22
24
  end
@@ -0,0 +1,207 @@
1
+ require "minitest/autorun"
2
+ require "minitest/pride"
3
+ require "./lib/rsyntaxtree/markup_parser.rb"
4
+
5
+ class MarkupParserTest < Minitest::Test
6
+ def setup
7
+ @parser = MarkupParser.new
8
+ end
9
+
10
+ def test_rule_cr
11
+ @parser.cr.parse('\\n')
12
+ # "\\n"@0
13
+ end
14
+
15
+ def test_rule_brackets
16
+ @parser.brackets.parse('#')
17
+ # "#"@0
18
+ end
19
+
20
+ def test_rule_triangle
21
+ @parser.triangle.parse('^')
22
+ # "^"@0
23
+ end
24
+
25
+ def test_rule_path
26
+ @parser.path.parse('+12')
27
+ # {:path=>"+12"@0}
28
+ @parser.path.parse('+>34')
29
+ # {:path=>"+>34"@0}d
30
+ end
31
+
32
+ def test_rule_escaped
33
+ "<>^+*_=~|-".split(//).each do |chr|
34
+ @parser.escaped.parse('\\' + chr)
35
+ # {:chr=>"<"@1}
36
+ # {:chr=>">"@1}
37
+ # {:chr=>"^"@1}
38
+ # {:chr=>"+"@1}
39
+ # {:chr=>"*"@1}
40
+ # {:chr=>"_"@1}
41
+ # {:chr=>"="@1}
42
+ # {:chr=>"~"@1}
43
+ # {:chr=>"|"@1}
44
+ # {:chr=>"-"@1}
45
+ end
46
+ end
47
+
48
+ def test_rule_non_escaped
49
+ "abcde12345".split(//).each do |chr|
50
+ @parser.non_escaped.parse(chr)
51
+ # {:chr=>"a"@0}
52
+ # {:chr=>"b"@0}
53
+ # {:chr=>"c"@0}
54
+ # {:chr=>"d"@0}
55
+ # {:chr=>"e"@0}
56
+ # {:chr=>"1"@0}
57
+ # {:chr=>"2"@0}
58
+ # {:chr=>"3"@0}
59
+ # {:chr=>"4"@0}
60
+ # {:chr=>"5"@0}
61
+ end
62
+ end
63
+
64
+ def test_rule_text
65
+ text = "abcde\\<\\>\\^\\+\\*\\_\\=\\~\\|\\-12345"
66
+ @parser.text.parse(text)
67
+ # {:text=>
68
+ # [{:chr=>"a"@0},
69
+ # {:chr=>"b"@1},
70
+ # {:chr=>"c"@2},
71
+ # {:chr=>"d"@3},
72
+ # {:chr=>"e"@4},
73
+ # {:chr=>"<"@6},
74
+ # {:chr=>">"@8},
75
+ # {:chr=>"^"@10},
76
+ # {:chr=>"+"@12},
77
+ # {:chr=>"*"@14},
78
+ # {:chr=>"_"@16},
79
+ # {:chr=>"="@18},
80
+ # {:chr=>"~"@20},
81
+ # {:chr=>"|"@22},
82
+ # {:chr=>"-"@24},
83
+ # {:chr=>"1"@25},
84
+ # {:chr=>"2"@26},
85
+ # {:chr=>"3"@27},
86
+ # {:chr=>"4"@28},
87
+ # {:chr=>"5"@29}]}
88
+ end
89
+
90
+ def test_rule_bolditalic
91
+ text = "***~=X\\+Y\\*Z=~***"
92
+ @parser.bolditalic.parse(text)
93
+ # {:bolditalic=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@5}, {:chr=>"+"@7}, {:chr=>"Y"@8}, {:chr=>"*"@10}, {:chr=>"Z"@11}]}}}}
94
+ end
95
+
96
+ def test_rule_bold
97
+ text = "**~=X\\+Y\\*Z=~**"
98
+ @parser.bold.parse(text)
99
+ # {:bold=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@4}, {:chr=>"+"@6}, {:chr=>"Y"@7}, {:chr=>"*"@9}, {:chr=>"Z"@10}]}}}}
100
+ end
101
+
102
+ def test_rule_italic
103
+ text = "*~=X\\+Y\\*Z=~*"
104
+ @parser.italic.parse(text)
105
+ # {:italic=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@3}, {:chr=>"+"@5}, {:chr=>"Y"@6}, {:chr=>"*"@8}, {:chr=>"Z"@9}]}}}}
106
+ end
107
+
108
+ def test_rule_overline
109
+ text = "=*~X\\+Y\\*Z~*="
110
+ @parser.overline.parse(text)
111
+ # {:overline=> ...}
112
+ end
113
+
114
+ def test_rule_underline
115
+ text = "-*~X\\+Y\\*Z~*-"
116
+ @parser.underline.parse(text)
117
+ # {:underline=> ...}
118
+ end
119
+
120
+ def test_rule_linethrough
121
+ text = "~*=X\\+Y\\*Z=*~"
122
+ @parser.linethrough.parse(text)
123
+ # {:linethrough=> ...}
124
+ end
125
+
126
+ def test_rule_superscript
127
+ text = "__~*=X\\+Y\\*Z=*~__"
128
+ @parser.superscript.parse(text)
129
+ # {:superscript=> ...}
130
+ end
131
+
132
+ def test_rule_subscript
133
+ text = "_~*=X\\+Y\\*Z=*~_"
134
+ @parser.subscript.parse(text)
135
+ # {:subscript=> ...}
136
+ end
137
+
138
+ def test_rule_box
139
+ text = "|~*=X\\+Y\\*Z=*~|"
140
+ @parser.box.parse(text)
141
+ # {:box=> ...}
142
+ end
143
+
144
+ def test_rule_markup
145
+ text = "|~*=X\\+Y\\*Z=*~|"
146
+ @parser.markup.parse(text)
147
+ # {:box=>{:linethrough=>{:italic=>{:overline=>{:text=>[
148
+ # {:chr=>"X"@4}, {:chr=>"+"@6}, {:chr=>"Y"@7}, {:chr=>"*"@9}, {:chr=>"Z"@10}]}}}}
149
+ end
150
+
151
+ def test_rule_border
152
+ text = "----"
153
+ @parser.border.parse(text)
154
+ # {:border=>"----"@0}
155
+ end
156
+
157
+ def test_rule_line
158
+ text1 = "----"
159
+ @parser.line.parse(text1)
160
+ # {:border=>"----"@0}
161
+ text2 = "-u-|b|n"
162
+ @parser.line.parse(text2)
163
+ # {:line=>[{:underline=>{:text=>[{:chr=>"u"@1}]}}, {:box=>{:text=>[{:chr=>"b"@4}]}}, {:text=>[{:chr=>"n"@6}]}]}
164
+ text3 = "\\n"
165
+ @parser.line.parse(text3)
166
+ # "\\n"@0
167
+ end
168
+
169
+ def test_rule_lines
170
+ text1 = "^#X_Y_Z"
171
+ @parser.lines.parse(text1)
172
+ # [{:triangle=>"^"@0, :brackets=>"#"@1}, {:line=>[{:text=>[{:chr=>"X"@2}]},
173
+ # {:subscript=>{:text=>[{:chr=>"Y"@4}]}}, {:text=>[{:chr=>"Z"@6}]}]}, {:paths=>[]}]
174
+ text2 = "^X_Y_Z+1+>2"
175
+ @parser.lines.parse(text2)
176
+ # [{:triangle=>"^"@0, :enclosure=>:none},
177
+ # {:line=>[{:text=>[{:chr=>"X"@2}]}, {:subscript=>{:text=>[{:chr=>"Y"@4}]}}, {:text=>[{:chr=>"Z"@6}]}]},
178
+ # {:paths=>[{:path=>"+1"@7}, {:path=>"+>2"@9}]}]
179
+ text3 = "^----\\n\\nX_Y_Z+1+>2"
180
+ @parser.lines.parse(text3)
181
+ # [{:triangle=>"^"@0, :enclosure=>:none},
182
+ # {:border=>"----"@1},
183
+ # {:blankline=>"\\n\\n"@5},
184
+ # {:line=>[{:text=>[{:chr=>"X"@9}]}, {:subscript=>{:text=>[{:chr=>"Y"@11}]}},
185
+ # {:text=>[{:chr=>"Z"@13}]}]},
186
+ # {:paths=>[{:path=>"+1"@14}, {:path=>"+>2"@16}]}]
187
+ end
188
+
189
+ def test_evaluator
190
+ text1 = "^#----\\n\\nX_Y_Z+1+>2"
191
+ Markup.parse text1
192
+ # {:status=>:success,
193
+ # :results=>
194
+ # {:enclosure=>:brackets,
195
+ # :triangle=>true,
196
+ # :paths=>[{:path=>"+1"@15}, {:path=>"+>2"@17}],
197
+ # :contents=>
198
+ # [{:type=>:border},
199
+ # {:type=>:blankline},
200
+ # {:type=>:text, :elements=>[{:text=>"X", :decoration=>[]},
201
+ # {:text=>"Y", :decoration=>[:subscript]}, {:text=>"Z", :decoration=>[]}]}]}}
202
+ text2 = "!^#----\\n\\nX_Y_Z+1+>2"
203
+ Markup.parse text2
204
+ # {:status=>:error, :text=>"!^#----\\n\\nX_Y_Z+1+>2"}
205
+ end
206
+
207
+ end