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
@@ -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