trenni 1.7.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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