rsyntaxtree 0.9.3 → 1.0.6

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.tags +179 -0
  4. data/Gemfile +2 -0
  5. data/README.md +35 -29
  6. data/Rakefile +7 -0
  7. data/bin/rsyntaxtree +42 -31
  8. data/fonts/OpenMoji-Black.ttf +0 -0
  9. data/fonts/OpenMoji-Color.ttf +0 -0
  10. data/img/elements/circle.png +0 -0
  11. data/img/elements/circle_abc.png +0 -0
  12. data/img/elements/circle_bold.png +0 -0
  13. data/img/elements/circle_hatched.png +0 -0
  14. data/img/elements/circle_one.png +0 -0
  15. data/img/elements/connector.png +0 -0
  16. data/img/elements/connector_bold.png +0 -0
  17. data/img/elements/square.png +0 -0
  18. data/img/elements/square_abc.png +0 -0
  19. data/img/elements/square_bold.png +0 -0
  20. data/img/elements/square_hatched.png +0 -0
  21. data/img/elements/square_one.png +0 -0
  22. data/img/rsyntaxtree.png +0 -0
  23. data/img/sample.png +0 -0
  24. data/img/sample.svg +38 -0
  25. data/lib/rsyntaxtree/base_graph.rb +262 -0
  26. data/lib/rsyntaxtree/element.rb +156 -25
  27. data/lib/rsyntaxtree/elementlist.rb +16 -13
  28. data/lib/rsyntaxtree/markup_parser.rb +208 -0
  29. data/lib/rsyntaxtree/string_parser.rb +187 -204
  30. data/lib/rsyntaxtree/svg_graph.rb +471 -298
  31. data/lib/rsyntaxtree/utils.rb +49 -6
  32. data/lib/rsyntaxtree/version.rb +1 -1
  33. data/lib/rsyntaxtree.rb +144 -161
  34. data/rsyntaxtree.gemspec +2 -0
  35. data/test/markup_parser_test.rb +207 -0
  36. metadata +52 -11
  37. data/fonts/latinmodern-math.otf +0 -0
  38. data/fonts/lmroman10-bold.otf +0 -0
  39. data/fonts/lmroman10-bolditalic.otf +0 -0
  40. data/fonts/lmroman10-italic.otf +0 -0
  41. data/fonts/lmroman10-regular.otf +0 -0
  42. data/lib/rsyntaxtree/error_message.rb +0 -68
  43. data/lib/rsyntaxtree/graph.rb +0 -312
  44. data/lib/rsyntaxtree/tree_graph.rb +0 -327
@@ -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.3"
2
+ VERSION = "1.0.6"
3
3
  end
4
4
 
data/lib/rsyntaxtree.rb CHANGED
@@ -7,186 +7,169 @@
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, :polyline
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
+ :polyline => false,
113
+ }
114
+
115
+ @params.merge! new_params
116
+ @params[:fontsize] = @params[:fontsize] * FONT_SCALING
117
+ @params[:margin] = @params[:margin] * FONT_SCALING
118
+ @params[:fontset] = fontset
119
+
120
+ $single_X_metrics = FontMetrics.get_metrics("X", fontset[:normal], @params[:fontsize], :normal, :normal)
121
+ $height_connector_to_text = $single_X_metrics.height / 2.0
122
+ $single_line_height = $single_X_metrics.height * 2.0
123
+ $width_half_X = $single_X_metrics.width / 2.0
124
+ $height_connector = $single_X_metrics.height * 1.0 * @params[:vheight]
125
+ $h_gap_between_nodes = $single_X_metrics.width * 0.8
126
+ $box_vertical_margin = $single_X_metrics.height * 0.8
127
+ end
29
128
 
30
- ETYPE_UNDEFINED = 0
31
- ETYPE_NODE = 1
32
- ETYPE_LEAF = 2
33
- SUBSCRIPT_CONST = 0.7
34
- FONT_SCALING = 2
129
+ def self.check_data(text)
130
+ if text.to_s == ""
131
+ raise RSTError, "Error: input text is empty"
132
+ end
133
+ StringParser.valid?(text)
134
+ end
35
135
 
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
136
+ def draw_png(binary = false)
137
+ svg = draw_svg
138
+ rsvg = RSVG::Handle.new_from_data(svg)
139
+ dim = rsvg.dimensions
140
+ surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, dim.width, dim.height)
141
+ context = Cairo::Context.new(surface)
142
+ context.render_rsvg_handle(rsvg)
143
+ b = StringIO.new
144
+ surface.write_to_png(b)
145
+ if binary
146
+ return b
95
147
  else
96
- new_params[key] = value
148
+ return b.string
97
149
  end
98
150
  end
99
151
 
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 => 14,
123
- :v_space => 20,
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
152
+ def draw_svg
153
+ sp = StringParser.new(@params[:data].gsub('&', '&amp;'), @params[:fontset], @params[:fontsize])
154
+ sp.parse
155
+ graph = SVGGraph.new(sp.get_elementlist, @params)
156
+ graph.svg_data
157
+ end
178
158
 
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])
159
+ def draw_tree
160
+ svg = draw_svg
161
+ image, data = Magick::Image.from_blob(svg) do |im|
162
+ im.format = 'svg'
163
+ end
164
+ if @params[:format] == "png"
165
+ image = draw_png(false)
166
+ else
167
+ image.to_blob do |im|
168
+ im.format = @params[:format].upcase
169
+ end
170
+ end
171
+ image
172
+ end
190
173
  end
191
174
  end
192
175
 
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