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
@@ -20,78 +20,104 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'trenni'
23
+ require 'trenni/template'
24
+ require 'trenni/parsers'
24
25
  require 'benchmark'
25
26
 
26
- # require 'ruby-prof'
27
+ RSpec.describe Trenni::Template do
28
+ let(:template_path) {File.expand_path('corpus/large.xhtml', __dir__)}
29
+ let(:template) {Trenni::Template.load_file template_path}
30
+ let(:output) {template.to_string}
31
+
32
+ it "should parse xhtml template and produce identical output" do
33
+ expect{output}.to_not raise_error
34
+ expect(output).to be == File.read(template_path)
35
+ end
36
+ end
27
37
 
28
- module Trenni::TemplateSpec
29
- describe Trenni::Template do
30
- let(:capture_template) {Trenni::Template.load_file File.expand_path('template_spec/capture.trenni', __dir__)}
31
-
32
- it "should be able to capture output" do
33
- expect(capture_template.to_string.strip).to be == 'TEST TEST TEST'
34
- end
35
-
36
- let(:buffer_template) {Trenni::Template.load_file File.expand_path('template_spec/buffer.trenni', __dir__)}
37
-
38
- it "should be able to fetch output buffer" do
39
- expect(buffer_template.to_string).to be == 'test'
40
- end
41
-
42
- let(:nested_template) {Trenni::Template.load_file File.expand_path('template_spec/nested.trenni', __dir__)}
38
+ RSpec.describe Trenni::Template do
39
+ let(:capture_template) {Trenni::Template.load_file File.expand_path('template_spec/capture.trenni', __dir__)}
40
+
41
+ it "should be able to capture output" do
42
+ expect(capture_template.to_string).to be == '"TEST TEST TEST\n"'
43
+ end
44
+
45
+ it "compiled template should match line numbers" do
46
+ code_lines = capture_template.send(:code).lines
43
47
 
44
- it "should be able to handle nested interpolations" do
45
- expect(nested_template.to_string).to be == "Hello world!"
46
- end
48
+ expect(code_lines.count).to be == 4
49
+ expect(code_lines[3]).to include("inspect")
50
+ end
51
+
52
+ let(:buffer_template) {Trenni::Template.load_file File.expand_path('template_spec/buffer.trenni', __dir__)}
53
+
54
+ it "should be able to fetch output buffer" do
55
+ expect(buffer_template.to_string).to be == 'test'
56
+ end
57
+
58
+ let(:nested_template) {Trenni::Template.load_file File.expand_path('template_spec/nested.trenni', __dir__)}
59
+
60
+ it "should be able to handle nested interpolations" do
61
+ expect(nested_template.to_string).to be == "Hello world!"
62
+ end
63
+
64
+ let(:items) {1..4}
65
+
66
+ it "should process list of items" do
67
+ buffer = Trenni::Buffer.new('<?r items.each do |item| ?>#{item}<?r end ?>')
68
+ template = Trenni::Template.new(buffer)
47
69
 
48
- it "should process list of items" do
49
- buffer = Trenni::Buffer.new('<?r items.each do |item| ?>#{item}<?r end ?>')
50
- template = Trenni::Template.new(buffer)
51
-
52
- items = 1..4
53
-
54
- expect(template.to_string(binding)).to be == "1234"
55
- end
70
+ expect(template.to_string(self)).to be == "1234"
71
+ end
72
+
73
+ it "should have correct indentation" do
74
+ buffer = Trenni::Buffer.new("\t<?r items.each do |item| ?>\n\t\#{item}\n\t<?r end ?>\n")
75
+ template = Trenni::Template.new(buffer)
56
76
 
57
- let(:large_template) {Trenni::Template.load_file File.expand_path('template_spec/large.trenni', __dir__)}
77
+ expect(template.to_string(self)).to be == "\t1\n\t2\n\t3\n\t4\n"
78
+ end
79
+
80
+ let(:escaped_template) {Trenni::Template.load_file File.expand_path('template_spec/escaped.trenni', __dir__)}
81
+
82
+ it "should have the same number of lines as input" do
83
+ expect(escaped_template.send(:code).lines.count).to be == 2
84
+ end
85
+
86
+ it "should process escaped characters" do
87
+ expect(escaped_template.to_string).to be ==
88
+ "This\\nisn't one line.\n" +
89
+ "\\tIndentation is the best."
90
+ end
91
+ end
58
92
 
59
- it "should have better performance using instance" do
60
- n = 1_000
61
-
62
- #RubyProf.start
63
-
64
- object_time = Benchmark.realtime{n.times{large_template.to_string(self)}}
65
- binding_time = Benchmark.realtime{n.times{large_template.to_string(binding)}}
66
-
67
- #result = RubyProf.stop
68
-
69
- # Print a flat profile to text
70
- #printer = RubyProf::FlatPrinter.new(result)
71
- #printer.print(STDOUT)
72
-
73
- expect(object_time).to be < binding_time
74
- end
93
+ RSpec.shared_examples "template parser" do
94
+ let(:delegate) {Trenni::ParseDelegate.new}
95
+
96
+ it "should fail to parse incomplete expression" do
97
+ buffer = Trenni::Buffer.new('<img src="#{poi_product.photo.thumbnail_url" />')
75
98
 
76
- let(:escaped_template) {Trenni::Template.load_file File.expand_path('template_spec/escaped.trenni', __dir__)}
77
-
78
- it "should have the same number of lines as input" do
79
- expect(escaped_template.send(:code).lines.count).to be == 2
80
- end
81
-
82
- it "should process escaped characters" do
83
- expect(escaped_template.to_string).to be ==
84
- "This\\nisn't one line.\n" +
85
- "\\tIndentation is the best."
86
- end
99
+ expect{
100
+ subject.parse_template(buffer, delegate)
101
+ }.to raise_error(Trenni::ParseError)
102
+ end
103
+
104
+ it "should fail to parse incomplete instruction" do
105
+ buffer = Trenni::Buffer.new('<?r foo')
87
106
 
88
- it "should fail to parse" do
89
- buffer = Trenni::Buffer.new('<img src="#{poi_product.photo.thumbnail_url" />')
90
- broken_template = Trenni::Template.new(buffer)
91
-
92
- expect{broken_template.to_proc}.to raise_error(Trenni::ParseError) do |error|
93
- expect(error.to_s).to include("<string>[1]: Could not find end of interpolation!")
94
- end
95
- end
107
+ expect{
108
+ subject.parse_template(buffer, delegate)
109
+ }.to raise_error(Trenni::ParseError)
96
110
  end
97
111
  end
112
+
113
+ if defined? Trenni::Fallback
114
+ RSpec.describe Trenni::Fallback do
115
+ it_behaves_like "template parser"
116
+ end
117
+ end
118
+
119
+ if defined? Trenni::Native
120
+ RSpec.describe Trenni::Native do
121
+ it_behaves_like "template parser"
122
+ end
123
+ end
@@ -0,0 +1 @@
1
+ #{text}
@@ -1,4 +1,4 @@
1
1
  <?r result = Trenni::Template.capture do ?>
2
- test test test
2
+ test test test
3
3
  <?r end ?>
4
- #{result.upcase}
4
+ #{result.upcase.inspect}
@@ -0,0 +1,4 @@
1
+ <?r
2
+
3
+ ?>
4
+ #{error_on_line_4}
data/trenni.gemspec CHANGED
@@ -9,12 +9,11 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Samuel Williams"]
10
10
  spec.email = ["samuel.williams@oriontransfer.co.nz"]
11
11
  spec.description = <<-EOF
12
- Trenni is a templating system that evaluates textual strings containing Ruby
13
- code. It compiles templates directly into native code which means that you
14
- generally get the best possible performance.
15
-
12
+ Trenni is a templating system built on top of SGML/XML. It uses efficient
13
+ native parsers where possible and compiles templates into efficient Ruby.
14
+
16
15
  In addition, Trenni includes an SGML/XML builder to assist with the generation
17
- of pleasantly formatted markup.
16
+ of pleasantly formatted markup which is compatible with the included parsers.
18
17
  EOF
19
18
  spec.summary = %q{A fast native templating system that compiles directly to Ruby code.}
20
19
  spec.homepage = "https://github.com/ioquatix/trenni"
@@ -24,7 +23,11 @@ Gem::Specification.new do |spec|
24
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
24
  spec.require_paths = ["lib"]
26
25
 
26
+ spec.required_ruby_version = '~> 2.1'
27
+
28
+ spec.extensions = %w[ext/trenni/extconf.rb]
29
+
27
30
  spec.add_development_dependency "bundler", "~> 1.3"
28
- spec.add_development_dependency "rspec", "~> 3.4.0"
31
+ spec.add_development_dependency "rspec", "~> 3.4"
29
32
  spec.add_development_dependency "rake"
30
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trenni
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-30 00:00:00.000000000 Z
11
+ date: 2016-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.4.0
33
+ version: '3.4'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.4.0
40
+ version: '3.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,14 +52,15 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description: "\tTrenni is a templating system that evaluates textual strings containing
56
- Ruby\n\tcode. It compiles templates directly into native code which means that you\n\tgenerally
57
- get the best possible performance.\n\n\tIn addition, Trenni includes an SGML/XML
58
- builder to assist with the generation\n\tof pleasantly formatted markup.\n"
55
+ description: "\tTrenni is a templating system built on top of SGML/XML. It uses efficient\n\tnative
56
+ parsers where possible and compiles templates into efficient Ruby.\n\t\n\tIn addition,
57
+ Trenni includes an SGML/XML builder to assist with the generation\n\tof pleasantly
58
+ formatted markup which is compatible with the included parsers.\n"
59
59
  email:
60
60
  - samuel.williams@oriontransfer.co.nz
61
61
  executables: []
62
- extensions: []
62
+ extensions:
63
+ - ext/trenni/extconf.rb
63
64
  extra_rdoc_files: []
64
65
  files:
65
66
  - ".gitignore"
@@ -69,21 +70,54 @@ files:
69
70
  - Gemfile
70
71
  - README.md
71
72
  - Rakefile
73
+ - benchmark/call_vs_yield.rb
74
+ - benchmark/interpolation_vs_concat.rb
72
75
  - benchmark/io_vs_string.rb
76
+ - entities.json
77
+ - ext/trenni/extconf.rb
78
+ - ext/trenni/markup.c
79
+ - ext/trenni/markup.h
80
+ - ext/trenni/markup.rl
81
+ - ext/trenni/template.c
82
+ - ext/trenni/template.h
83
+ - ext/trenni/template.rl
84
+ - ext/trenni/trenni.c
85
+ - ext/trenni/trenni.h
73
86
  - lib/trenni.rb
74
87
  - lib/trenni/buffer.rb
75
88
  - lib/trenni/builder.rb
76
- - lib/trenni/parser.rb
77
- - lib/trenni/scanner.rb
89
+ - lib/trenni/entities.rb
90
+ - lib/trenni/entities.trenni
91
+ - lib/trenni/fallback/markup.rb
92
+ - lib/trenni/fallback/markup.rl
93
+ - lib/trenni/fallback/template.rb
94
+ - lib/trenni/fallback/template.rl
95
+ - lib/trenni/markup.rb
96
+ - lib/trenni/native.rb
97
+ - lib/trenni/parse_delegate.rb
98
+ - lib/trenni/parse_error.rb
99
+ - lib/trenni/parsers.rb
78
100
  - lib/trenni/strings.rb
101
+ - lib/trenni/substitutions.rb
79
102
  - lib/trenni/template.rb
80
103
  - lib/trenni/version.rb
104
+ - parsers/trenni/entities.rl
105
+ - parsers/trenni/markup.rl
106
+ - parsers/trenni/template.rl
81
107
  - spec/trenni/builder_spec.rb
82
- - spec/trenni/parser_spec.rb
108
+ - spec/trenni/corpus/large.rb
109
+ - spec/trenni/corpus/large.xhtml
110
+ - spec/trenni/markup_parser_spec.rb
111
+ - spec/trenni/markup_spec.rb
112
+ - spec/trenni/parsers_performance_spec.rb
83
113
  - spec/trenni/strings_spec.rb
114
+ - spec/trenni/template_error_spec.rb
115
+ - spec/trenni/template_performance_spec.rb
84
116
  - spec/trenni/template_spec.rb
117
+ - spec/trenni/template_spec/basic.trenni
85
118
  - spec/trenni/template_spec/buffer.trenni
86
119
  - spec/trenni/template_spec/capture.trenni
120
+ - spec/trenni/template_spec/error.trenni
87
121
  - spec/trenni/template_spec/escaped.trenni
88
122
  - spec/trenni/template_spec/large.trenni
89
123
  - spec/trenni/template_spec/nested.trenni
@@ -97,9 +131,9 @@ require_paths:
97
131
  - lib
98
132
  required_ruby_version: !ruby/object:Gem::Requirement
99
133
  requirements:
100
- - - ">="
134
+ - - "~>"
101
135
  - !ruby/object:Gem::Version
102
- version: '0'
136
+ version: '2.1'
103
137
  required_rubygems_version: !ruby/object:Gem::Requirement
104
138
  requirements:
105
139
  - - ">="
@@ -113,11 +147,19 @@ specification_version: 4
113
147
  summary: A fast native templating system that compiles directly to Ruby code.
114
148
  test_files:
115
149
  - spec/trenni/builder_spec.rb
116
- - spec/trenni/parser_spec.rb
150
+ - spec/trenni/corpus/large.rb
151
+ - spec/trenni/corpus/large.xhtml
152
+ - spec/trenni/markup_parser_spec.rb
153
+ - spec/trenni/markup_spec.rb
154
+ - spec/trenni/parsers_performance_spec.rb
117
155
  - spec/trenni/strings_spec.rb
156
+ - spec/trenni/template_error_spec.rb
157
+ - spec/trenni/template_performance_spec.rb
118
158
  - spec/trenni/template_spec.rb
159
+ - spec/trenni/template_spec/basic.trenni
119
160
  - spec/trenni/template_spec/buffer.trenni
120
161
  - spec/trenni/template_spec/capture.trenni
162
+ - spec/trenni/template_spec/error.trenni
121
163
  - spec/trenni/template_spec/escaped.trenni
122
164
  - spec/trenni/template_spec/large.trenni
123
165
  - spec/trenni/template_spec/nested.trenni
data/lib/trenni/parser.rb DELETED
@@ -1,153 +0,0 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require_relative 'scanner'
22
-
23
- module Trenni
24
- # This parser processes general markup into a sequence of events which are passed to a delegate.
25
- class Parser < StringScanner
26
- OPENED_TAG = :opened
27
- CLOSED_TAG = :closed
28
-
29
- def initialize(buffer, delegate)
30
- super(buffer)
31
-
32
- @delegate = delegate
33
-
34
- # The delegate must respond to:
35
- # .begin_parse(self)
36
- # .text(escaped_data)
37
- # .cdata(unescaped_data)
38
- # .attribute(name, value_or_true)
39
- # .begin_tag(name, :opened or :closed)
40
- # .end_tag(begin_tag_type, :opened or :closed)
41
- # .doctype(doctype_attributes)
42
- # .comment(comment_text)
43
- # .instruction(instruction_text)
44
- end
45
-
46
- def parse!
47
- @delegate.begin_parse(self)
48
-
49
- until eos?
50
- start_pos = self.pos
51
-
52
- scan_text
53
- scan_tag
54
-
55
- raise_if_stuck(start_pos)
56
- end
57
- end
58
-
59
- protected
60
-
61
- def scan_text
62
- # Match any character data except the open tag character.
63
- if self.scan(/[^<]+/m)
64
- @delegate.text(self.matched)
65
- end
66
- end
67
-
68
- def scan_tag
69
- if self.scan(/</)
70
- if self.scan(/\//)
71
- scan_tag_normal(CLOSED_TAG)
72
- elsif self.scan(/!\[CDATA\[/)
73
- scan_tag_cdata
74
- elsif self.scan(/!--/)
75
- scan_tag_comment
76
- elsif self.scan(/!DOCTYPE/)
77
- scan_doctype
78
- elsif self.scan(/\?/)
79
- scan_tag_instruction
80
- else
81
- scan_tag_normal
82
- end
83
- end
84
- end
85
-
86
- def scan_attributes
87
- # Parse an attribute in the form of key="value" or key.
88
- while self.scan(/\s*([^\s=\/>]+)/um)
89
- name = self[1].freeze
90
- if self.scan(/=((['"])(.*?)\2)/um)
91
- value = self[3].freeze
92
- @delegate.attribute(name, value)
93
- else
94
- @delegate.attribute(name, true)
95
- end
96
- end
97
- end
98
-
99
- def scan_tag_normal(begin_tag_type = OPENED_TAG)
100
- if self.scan(/[^\s\/>]+/)
101
- @delegate.begin_tag(self.matched.freeze, begin_tag_type)
102
-
103
- self.scan(/\s*/)
104
- self.scan_attributes
105
- self.scan(/\s*/)
106
-
107
- if self.scan(/\/>/)
108
- if begin_tag_type == CLOSED_TAG
109
- parse_error!("Tag cannot be closed at both ends!")
110
- else
111
- @delegate.finish_tag(begin_tag_type, CLOSED_TAG)
112
- end
113
- elsif self.scan(/>/)
114
- @delegate.finish_tag(begin_tag_type, OPENED_TAG)
115
- else
116
- parse_error!("Invalid characters in tag!")
117
- end
118
- else
119
- parse_error!("Invalid tag!")
120
- end
121
- end
122
-
123
- def scan_doctype
124
- if self.scan_until(/(.*?)>/)
125
- @delegate.doctype(self[1].strip.freeze)
126
- else
127
- parse_error!("DOCTYPE is not closed!")
128
- end
129
- end
130
-
131
- def scan_tag_cdata
132
- if self.scan_until(/(.*?)\]\]>/m)
133
- @delegate.cdata(self[1].freeze)
134
- else
135
- parse_error!("CDATA segment is not closed!")
136
- end
137
- end
138
-
139
- def scan_tag_comment
140
- if self.scan_until(/(.*?)-->/m)
141
- @delegate.comment(self[1].freeze)
142
- else
143
- parse_error!("Comment is not closed!")
144
- end
145
- end
146
-
147
- def scan_tag_instruction
148
- if self.scan_until(/(.*)\?>/)
149
- @delegate.instruction(self[1].freeze)
150
- end
151
- end
152
- end
153
- end