rsyntaxtree 0.9.1 → 1.0.4

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.9.1"
2
+ VERSION = "1.0.4"
3
3
  end
4
4
 
data/lib/rsyntaxtree.rb CHANGED
@@ -7,186 +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 = value
44
- # data = CGI.unescape(value)
45
- data = data.gsub('-AMP-', '&')
46
- .gsub('-PERCENT-', "%")
47
- .gsub('-PRIME-', "'")
48
- .gsub('-SCOLON-', ';')
49
- .gsub('-OABRACKET-', '<')
50
- .gsub('-CABRACKET-', '>')
51
- new_params[key] = data
52
- new_params[:multibyte] = data.contains_cjk?
53
- when :symmetrize, :color, :autosub, :transparent
54
- new_params[key] = value && value != "off" ? true : false
55
- when :fontsize
56
- new_params[key] = value.to_i * FONT_SCALING
57
- when :margin
58
- new_params[key] = value.to_i * FONT_SCALING * 4
59
- when :vheight
60
- new_params[key] = value.to_f
61
- when :fontstyle
62
- if value == "noto-sans" || value == "sans"
63
- new_params[:font] = FONT_DIR + "/NotoSans-Regular.ttf"
64
- new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
65
- new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
66
- new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
67
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
68
- new_params[:font_cjk] = FONT_DIR + "/NotoSansJP-Regular.otf"
69
- new_params[:fontstyle] = "sans"
70
- elsif value == "noto-serif" || value == "serif"
71
- new_params[:font] = FONT_DIR + "/NotoSerif-Regular.ttf"
72
- new_params[:font_it] = FONT_DIR + "/NotoSerif-Italic.ttf"
73
- new_params[:font_bd] = FONT_DIR + "/NotoSerif-Bold.ttf"
74
- new_params[:font_bdit] = FONT_DIR + "/NotoSerif-BoldItalic.ttf"
75
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
76
- new_params[:font_cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
77
- new_params[:fontstyle] = "serif"
78
- elsif value == "cjk zenhei" || value == "cjk"
79
- new_params[:font] = FONT_DIR + "/wqy-zenhei.ttf"
80
- new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
81
- new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
82
- new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
83
- new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
84
- new_params[:font_cjk] = FONT_DIR + "/wqy-zenhei.ttf"
85
- new_params[:fontstyle] = "sans"
86
- elsif value == "modern-math" || value == "math"
87
- new_params[:font] = FONT_DIR + "/latinmodern-math.otf"
88
- new_params[:font_it] = FONT_DIR + "/lmroman10-italic.otf"
89
- new_params[:font_bd] = FONT_DIR + "/lmroman10-bold.otf"
90
- new_params[:font_bdit] = FONT_DIR + "/lmroman10-bolditalic.otf"
91
- new_params[:font_math] = FONT_DIR + "/latinmodern-math.otf"
92
- new_params[:font_cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
93
- new_params[:fontstyle] = "math"
94
- 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
95
146
  else
96
- new_params[key] = value
147
+ return b.string
97
148
  end
98
149
  end
99
150
 
100
- # defaults to the following
101
- @params = {
102
- :symmetrize => true,
103
- :color => true,
104
- :autosub => false,
105
- :transparent => false,
106
- :fontsize => 18,
107
- :format => "png",
108
- :leafstyle => "auto",
109
- :filename => "syntree",
110
- :data => "",
111
- :margin => 0,
112
- :vheight => 1.0,
113
- :fontstyle => "sans",
114
- :font => "/NotoSansJP-Regular.otf",
115
- :font_it => "/NotoSans-Italic.ttf",
116
- :font_bd => "/NotoSans-Bold.ttf",
117
- :font_bdit => "/NotoSans-BoldItalic.ttf",
118
- :font_math => "/NotoSansMath-Regular.ttf"
119
- }
120
- @metrics = {
121
- :e_width => 0,
122
- :e_padd => 20,
123
- :v_space => 0,
124
- :h_space => 0,
125
- :b_side => 0,
126
- :b_topbot => 0
127
- # :e_width => 120,
128
- # :e_padd => 14,
129
- # :v_space => 20,
130
- # :h_space => 20,
131
- # :b_side => 10,
132
- # :b_topbot => 10
133
- }
134
-
135
- @params.merge! new_params
136
- @params[:fontsize] = @params[:fontsize] * FONT_SCALING
137
- @params[:margin] = @params[:margin] * FONT_SCALING
138
- @metrics[:v_space] = @metrics[:v_space] * @params[:vheight]
139
- end
140
-
141
- def self.check_data(text)
142
- sp = StringParser.new(text)
143
- sp.valid?
144
- end
145
-
146
- def draw_png
147
- @params[:format] = "png"
148
- draw_tree
149
- end
150
-
151
- def draw_jpg
152
- @params[:format] = "jpg"
153
- draw_tree
154
- end
155
-
156
- def draw_gif
157
- @params[:format] = "gif"
158
- draw_tree
159
- end
160
-
161
- def draw_pdf
162
- @params[:format] = "pdf"
163
- draw_tree
164
- end
165
-
166
- def draw_svg
167
- @params[:format] = "svg"
168
- sp = StringParser.new(@params[:data].gsub('&', '&amp;').gsub('%', '&#37;'))
169
- sp.parse
170
- sp.auto_subscript if @params[:autosub]
171
- elist = sp.get_elementlist
172
- graph = SVGGraph.new(elist, @metrics,
173
- @params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
174
- @params[:fontstyle], @params[:font], @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
175
- )
176
- graph.svg_data
177
- end
151
+ def draw_svg
152
+ sp = StringParser.new(@params[:data].gsub('&', '&amp;').gsub('%', '%'), @params[:fontset], @params[:fontsize])
153
+ sp.parse
154
+ graph = SVGGraph.new(sp.get_elementlist, @params)
155
+ graph.svg_data
156
+ end
178
157
 
179
- def draw_tree
180
- sp = StringParser.new(@params[:data])
181
- sp.parse
182
- sp.auto_subscript if @params[:autosub]
183
- elist = sp.get_elementlist
184
- graph = TreeGraph.new(elist, @metrics,
185
- @params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
186
- @params[:fontstyle], @params[:font], @params[:font_it], @params[:font_bd], @params[:font_bdit], @params[:font_math],
187
- @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
188
- )
189
- 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
190
172
  end
191
173
  end
192
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
data/syntree.png ADDED
Binary file
data/syntree.svg ADDED
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg width="161.4" height="471.2" viewBox="-20, -20, 181.4, 491.2" version="1.1" xmlns="http://www.w3.org/2000/svg">
4
+ <defs>
5
+ <marker id="arrow" markerUnits="strokeWidth" markerWidth="12.160000000000002" markerHeight="12.160000000000002" viewBox="0 0 12.160000000000002 12.160000000000002" refX="6.080000000000001" refY="0">
6
+ <polyline fill="none" stroke="purple" stroke-width="1" points="0,12.160000000000002,6.080000000000001,0,12.160000000000002,12.160000000000002" />
7
+ </marker>
8
+ <pattern id="hatchBlack" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
9
+ <line x1="0" y="0" x2="0" y2="10" stroke="black" stroke-width="4"></line>
10
+ </pattern>
11
+ <pattern id="hatchForNode" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
12
+ <line x1="0" y="0" x2="0" y2="10" stroke="blue" stroke-width="4"></line>
13
+ </pattern>
14
+ <pattern id="hatchForLeaf" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
15
+ <line x1="0" y="0" x2="0" y2="10" stroke="green" stroke-width="4"></line>
16
+ </pattern>
17
+ </defs>
18
+ <rect x="-20" y="-20" width="181.4" height="491.2" stroke="none" fill="white" />"
19
+ <text white-space='pre' alignment-baseline='text-top' style='fill: blue; font-size: 64px;' x='30.400000000000006' y='132.0'><tspan x='30.400000000000006' y='132.0' style="" text-decoration="" font-family="'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif">X%</tspan>
20
+ </text>
21
+ <text white-space='pre' alignment-baseline='text-top' style='fill: green; font-size: 64px;' x='33.400000000000006' y='431.2'><tspan x='33.400000000000006' y='431.2' style="" text-decoration="" font-family="'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif">X&amp;</tspan>
22
+ </text>
23
+ <line style='stroke:black; stroke-width:2;' x1='75.9' y1='321.2' x2='75.9' y2='187.0' />
24
+ </svg>
@@ -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