trenni 1.7.0 → 2.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +7 -3
  4. data/Gemfile +4 -0
  5. data/README.md +72 -10
  6. data/Rakefile +85 -0
  7. data/benchmark/call_vs_yield.rb +51 -0
  8. data/benchmark/interpolation_vs_concat.rb +29 -0
  9. data/benchmark/io_vs_string.rb +14 -4
  10. data/entities.json +2233 -0
  11. data/ext/trenni/extconf.rb +13 -0
  12. data/ext/trenni/markup.c +1981 -0
  13. data/ext/trenni/markup.h +6 -0
  14. data/ext/trenni/markup.rl +223 -0
  15. data/ext/trenni/template.c +1113 -0
  16. data/ext/trenni/template.h +6 -0
  17. data/ext/trenni/template.rl +77 -0
  18. data/ext/trenni/trenni.c +64 -0
  19. data/ext/trenni/trenni.h +28 -0
  20. data/lib/trenni.rb +1 -0
  21. data/lib/trenni/buffer.rb +13 -2
  22. data/lib/trenni/builder.rb +54 -47
  23. data/lib/trenni/entities.rb +2154 -0
  24. data/lib/trenni/entities.trenni +34 -0
  25. data/lib/trenni/fallback/markup.rb +1648 -0
  26. data/lib/trenni/fallback/markup.rl +236 -0
  27. data/lib/trenni/fallback/template.rb +843 -0
  28. data/lib/trenni/fallback/template.rl +97 -0
  29. data/lib/trenni/markup.rb +76 -0
  30. data/lib/trenni/native.rb +28 -0
  31. data/lib/trenni/parse_delegate.rb +34 -0
  32. data/lib/trenni/{scanner.rb → parse_error.rb} +9 -54
  33. data/lib/trenni/parsers.rb +12 -0
  34. data/lib/trenni/substitutions.rb +45 -0
  35. data/lib/trenni/template.rb +52 -135
  36. data/lib/trenni/version.rb +1 -1
  37. data/parsers/trenni/entities.rl +11 -0
  38. data/parsers/trenni/markup.rl +43 -0
  39. data/parsers/trenni/template.rl +60 -0
  40. data/spec/trenni/builder_spec.rb +37 -62
  41. data/spec/trenni/corpus/large.rb +4605 -0
  42. data/spec/trenni/corpus/large.xhtml +726 -0
  43. data/spec/trenni/markup_parser_spec.rb +233 -0
  44. data/spec/trenni/markup_spec.rb +48 -0
  45. data/spec/trenni/parsers_performance_spec.rb +44 -0
  46. data/spec/trenni/template_error_spec.rb +36 -0
  47. data/spec/trenni/template_performance_spec.rb +102 -0
  48. data/spec/trenni/template_spec.rb +90 -64
  49. data/spec/trenni/template_spec/basic.trenni +1 -0
  50. data/spec/trenni/template_spec/capture.trenni +2 -2
  51. data/spec/trenni/template_spec/error.trenni +4 -0
  52. data/trenni.gemspec +9 -6
  53. metadata +57 -15
  54. data/lib/trenni/parser.rb +0 -153
  55. data/spec/trenni/parser_spec.rb +0 -144
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'trenni/parsers'
24
+ require 'trenni/entities'
25
+ require 'trenni/template'
26
+ require 'trenni/markup'
27
+
28
+ RSpec.shared_context "html parsers" do
29
+ let(:delegate) {Trenni::ParseDelegate.new}
30
+ let(:buffer) {Trenni::Buffer(subject)}
31
+ let(:parsers) {Trenni::Parsers}
32
+ let(:entities) {Trenni::Entities::HTML5}
33
+ let(:events) {parsers.parse_markup(buffer, delegate, entities); delegate.events}
34
+ end
35
+
36
+ RSpec.shared_context "valid markup" do
37
+ include_context "html parsers"
38
+
39
+ it "should parse without error" do
40
+ expect{events}.to_not raise_error
41
+ end
42
+ end
43
+
44
+ RSpec.describe "<br/>" do
45
+ include_context "valid markup"
46
+
47
+ it "should parse self-closing tag" do
48
+ expect(events).to be == [
49
+ [:open_tag_begin, "br", 1],
50
+ [:open_tag_end, true],
51
+ ]
52
+ end
53
+ end
54
+
55
+ RSpec.describe "<!DOCTYPE html>" do
56
+ include_context "valid markup"
57
+
58
+ it "should parse doctype" do
59
+ expect(events).to be == [
60
+ [:doctype, "<!DOCTYPE html>"]
61
+ ]
62
+ end
63
+ end
64
+
65
+ RSpec.describe "<?r foo=bar?>" do
66
+ include_context "valid markup"
67
+
68
+ it "should parse instruction" do
69
+ expect(events).to be == [
70
+ [:instruction, "<?r foo=bar?>"]
71
+ ]
72
+ end
73
+ end
74
+
75
+ RSpec.describe %Q{<!--comment-->} do
76
+ include_context "valid markup"
77
+
78
+ it "should parse comment" do
79
+ expect(events).to be == [
80
+ [:comment, "<!--comment-->"]
81
+ ]
82
+ end
83
+ end
84
+
85
+ RSpec.describe "<tag key=\"A&amp;B\" />" do
86
+ include_context "valid markup"
87
+
88
+ it "should parse escaped attributes" do
89
+ expect(events).to be == [
90
+ [:open_tag_begin, "tag", 1],
91
+ [:attribute, "key", "A&B"],
92
+ [:open_tag_end, true],
93
+ ]
94
+ end
95
+ end
96
+
97
+ RSpec.describe "<foo bar=\"20\" baz>Hello World</foo>" do
98
+ include_context "valid markup"
99
+
100
+ it "should parse tag with content" do
101
+ expect(events).to be == [
102
+ [:open_tag_begin, "foo", 1],
103
+ [:attribute, "bar", "20"],
104
+ [:attribute, "baz", true],
105
+ [:open_tag_end, false],
106
+ [:text, "Hello World"],
107
+ [:close_tag, "foo", 31],
108
+ ]
109
+ end
110
+
111
+ it "should have same encoding" do
112
+ expect(events[0][1].encoding).to be == subject.encoding
113
+ expect(events[1][1].encoding).to be == subject.encoding
114
+ expect(events[2][1].encoding).to be == subject.encoding
115
+ expect(events[4][1].encoding).to be == subject.encoding
116
+ expect(events[5][1].encoding).to be == subject.encoding
117
+ end
118
+ end
119
+
120
+ RSpec.describe "<test><![CDATA[Hello World]]></test>" do
121
+ include_context "valid markup"
122
+
123
+ it "should parse CDATA" do
124
+ expect(events).to be == [
125
+ [:open_tag_begin, "test", 1],
126
+ [:open_tag_end, false],
127
+ [:cdata, "<![CDATA[Hello World]]>"],
128
+ [:close_tag, "test", 31],
129
+ ]
130
+ end
131
+ end
132
+
133
+ RSpec.describe "<foo bar=\"\" baz />" do
134
+ include_context "valid markup"
135
+
136
+ it "should parse empty attributes" do
137
+ expect(events).to be == [
138
+ [:open_tag_begin, "foo", 1],
139
+ [:attribute, "bar", ""],
140
+ [:attribute, "baz", true],
141
+ [:open_tag_end, true],
142
+ ]
143
+ end
144
+ end
145
+
146
+ RSpec.describe "<p attr=\"foo&amp;bar\">&quot;</p>" do
147
+ include_context "valid markup"
148
+
149
+ let(:template_text) {%q{<p attr="#{events[1][2]}">#{events[3][1]}</p>}}
150
+ let(:template_buffer) {Trenni::Buffer(template_text)}
151
+ let(:template) {Trenni::MarkupTemplate.new(template_buffer)}
152
+
153
+ it "generates same output as input" do
154
+ result = template.to_string(self)
155
+ expect(result).to be == subject
156
+ end
157
+ end
158
+
159
+ RSpec.shared_examples "valid markup file" do |base|
160
+ let(:xhtml_path) {File.join(__dir__, base + '.xhtml')}
161
+ let(:events_path) {File.join(__dir__, base + '.rb')}
162
+
163
+ subject {Trenni::FileBuffer.new(xhtml_path)}
164
+ let(:expected_events) {eval(File.read(events_path), nil, events_path)}
165
+
166
+ include_context "valid markup"
167
+
168
+ def dump_events!
169
+ File.open(events_path, "w+") do |output|
170
+ output.puts "["
171
+ events.each do |event|
172
+ output.puts "\t#{event.inspect},"
173
+ end
174
+ output.puts "]"
175
+ end
176
+ end
177
+
178
+ it "should match events" do
179
+ #dump_events!
180
+
181
+ expected_events.each_with_index do |event, index|
182
+ expect(events[index]).to be == event
183
+ end
184
+ end
185
+ end
186
+
187
+ RSpec.describe "corpus/large" do
188
+ it_behaves_like "valid markup file", description
189
+ end
190
+
191
+ RSpec.shared_context "invalid markup" do
192
+ include_context "html parsers"
193
+
194
+ it "should fail to parse" do
195
+ expect{events}.to raise_error Trenni::ParseError
196
+ end
197
+ end
198
+
199
+ RSpec.describe "<foo" do
200
+ include_context "invalid markup"
201
+ end
202
+
203
+ RSpec.describe "<foo bar=>" do
204
+ include_context "invalid markup"
205
+ end
206
+
207
+ RSpec.describe "<p>\nこんにちは World<p" do
208
+ include_context "invalid markup"
209
+
210
+ let(:error) {events rescue $!}
211
+
212
+ it "should fail on line 2" do
213
+ expect(error.location.line_number).to be == 2
214
+ end
215
+
216
+ it "should fail at offset 23" do
217
+ expect(error.location.line_offset).to be == 23
218
+ end
219
+ end
220
+
221
+ RSpec.describe Trenni::Location do
222
+ subject{described_class.new("Hello\nWorld\nFoo\nBar!", 7)}
223
+
224
+ it "should know about line numbers" do
225
+ expect(subject.to_i).to be == 7
226
+ expect(subject.to_s).to be == "[2:1]"
227
+ expect(subject.line_text).to be == "World"
228
+
229
+ expect(subject.line_number).to be == 2
230
+ expect(subject.line_range.min).to be == 6
231
+ expect(subject.line_offset).to be == 1
232
+ end
233
+ end
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'trenni'
24
+ require 'trenni/markup'
25
+
26
+ RSpec.describe Trenni::MarkupString do
27
+ let(:template) {Trenni::MarkupTemplate.load_file File.expand_path('template_spec/basic.trenni', __dir__)}
28
+
29
+ let(:html_text) {"<h1>Hello World</h1>"}
30
+
31
+ it "should escape unsafe text" do
32
+ model = double(text: html_text)
33
+
34
+ expect(template.to_string(model)).to be == "&lt;h1&gt;Hello World&lt;/h1&gt;"
35
+ end
36
+
37
+ let(:safe_html_text) {Trenni::Builder.tag('h1', 'Hello World')}
38
+
39
+ it "should not escape safe text" do
40
+ model = double(text: safe_html_text)
41
+
42
+ expect(template.to_string(model)).to be == html_text
43
+ end
44
+
45
+ it "should convert nil to empty string" do
46
+ expect(Trenni::MarkupString(nil)).to be == ""
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+
2
+ require 'benchmark/ips'
3
+ require 'trenni/parsers'
4
+ require 'trenni/entities'
5
+
6
+ require 'ruby-prof'
7
+
8
+ RSpec.shared_context "profile" do
9
+ before(:all) do
10
+ RubyProf.start
11
+ end
12
+
13
+ after(:all) do
14
+ result = RubyProf.stop
15
+
16
+ # Print a flat profile to text
17
+ printer = RubyProf::FlatPrinter.new(result)
18
+ printer.print(STDOUT)
19
+ end
20
+ end
21
+
22
+ RSpec.describe Trenni::Parsers do
23
+ # include_context "profile"
24
+
25
+ let(:xhtml_path) {File.expand_path('corpus/large.xhtml', __dir__)}
26
+ let(:xhtml_buffer) {Trenni::FileBuffer.new(xhtml_path)}
27
+ let(:entities) {Trenni::Entities::HTML5}
28
+
29
+ it "should be fast to parse large documents" do
30
+ Benchmark.ips do |x|
31
+ x.report("Large Document") do |times|
32
+ delegate = Trenni::ParseDelegate.new
33
+
34
+ while (times -= 1) >= 0
35
+ Trenni::Parsers.parse_markup(xhtml_buffer, delegate, entities)
36
+
37
+ delegate.events.clear
38
+ end
39
+ end
40
+
41
+ x.compare!
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'trenni/template'
24
+ require 'trenni/parsers'
25
+
26
+ RSpec.describe Trenni::Template do
27
+ let(:template_path) {File.expand_path('template_spec/error.trenni', __dir__)}
28
+ let(:template) {Trenni::Template.load_file template_path}
29
+ let(:output) {template.to_string}
30
+
31
+ it "should produce error on correct line" do
32
+ expect{output}.to raise_error(NameError) do |error|
33
+ expect(error.backtrace.first).to be =~ /error.trenni:4:/
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,102 @@
1
+
2
+ require 'benchmark/ips'
3
+ require 'trenni/parsers'
4
+ require 'trenni/template'
5
+ require 'erb'
6
+
7
+ # require 'ruby-prof'
8
+
9
+ RSpec.describe Trenni::Template do
10
+ class Model
11
+ def name
12
+ "Bob Dole"
13
+ end
14
+
15
+ def reverse(&block)
16
+ Trenni::Template.capture(&block).reverse
17
+ end
18
+ end
19
+
20
+ # let(:large_template) {Trenni::Template.load_file File.expand_path('template_spec/large.trenni', __dir__)}
21
+ #
22
+ # it "should have better performance using instance" do
23
+ # n = 1_000
24
+ #
25
+ # #RubyProf.start
26
+ #
27
+ # object_time = Benchmark.realtime{n.times{large_template.to_string(self)}}
28
+ # binding_time = Benchmark.realtime{n.times{large_template.to_string(binding)}}
29
+ #
30
+ # #result = RubyProf.stop
31
+ #
32
+ # # Print a flat profile to text
33
+ # #printer = RubyProf::FlatPrinter.new(result)
34
+ # #printer.print(STDOUT)
35
+ #
36
+ # expect(object_time).to be < binding_time
37
+ # end
38
+
39
+
40
+ it "should be fast for basic templates" do
41
+ trenni_template = Trenni::Template.new(Trenni::Buffer.load('Hi, #{name}!'))
42
+ model = Model.new
43
+ model_binding = model.instance_eval{binding}
44
+
45
+ erb_template = ERB.new("Hi, <%= name %>!")
46
+
47
+ # There IS a measuarble difference:
48
+ Benchmark.ips do |x|
49
+ x.report("Trenni (object)") do |times|
50
+ i = 0
51
+
52
+ while i < times
53
+ trenni_template.to_string(model)
54
+
55
+ i += 1
56
+ end
57
+ end
58
+
59
+ # x.report("Trenni (binding)") do |times|
60
+ # i = 0
61
+ #
62
+ # while i < times
63
+ # trenni_template.to_string(model_binding)
64
+ #
65
+ # i += 1
66
+ # end
67
+ # end
68
+
69
+ x.report("ERB (binding)") do |times|
70
+ i = 0
71
+
72
+ while i < times
73
+ erb_template.result(model_binding)
74
+
75
+ i += 1
76
+ end
77
+ end
78
+
79
+ x.compare!
80
+ end
81
+ end
82
+
83
+ it "should be fast with capture" do
84
+ trenni_template = Trenni::Template.new(Trenni::Buffer.load('Hi, <?r reverse do ?>#{name}!<?r end ?>'))
85
+ model = Model.new
86
+
87
+ # There IS a measuarble difference:
88
+ Benchmark.ips do |x|
89
+ x.report("Trenni") do |times|
90
+ i = 0
91
+
92
+ while i < times
93
+ trenni_template.to_string(model)
94
+
95
+ i += 1
96
+ end
97
+ end
98
+
99
+ x.compare!
100
+ end
101
+ end
102
+ end