trenni 1.4.5 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35f057654b1e83a57d1fe72969c9ee72eaf376de
4
- data.tar.gz: a96c7a44b65be07ed3dc69e20ddbb25d92ee300a
3
+ metadata.gz: 5ff8d7a73ba2ef5c153eff38d0869b2d6cb53a91
4
+ data.tar.gz: 5a8a5f044ea099e6d69fc17d1481f47f2a678f47
5
5
  SHA512:
6
- metadata.gz: cef23ac0aa45af2f913cdd6b864a5beeacb78e0b3bc1c621db0565de40a34f030df816f048341a8c582f79628b75c334e33de3580c869956c7b0884856a48e21
7
- data.tar.gz: e6a318ab7acaa6a04c5857b51caecb019936b07dcdcdd50581d0cb7879a2260ac8b1c399fdd28660cfd4cd87fa247649d89a3da54205b7bee15ede9a7e890bd4
6
+ metadata.gz: 505a3e2d978b00b89764a085402d94d5d15c6c8230ed91f4f6f5970d6c5752f86af98d176e9be7bbd40d9b294ad35d6e13b08194d4f846706444bc5a916f13ac
7
+ data.tar.gz: a08e384c2cf0487b9567e972b701f8b5d70b0b8d737e579bc7c76fc9586228657e1cee554d099ffc45b23d6150f7ad0a8a5bf3c976cfc4e39166c3e7cd023000
data/Gemfile CHANGED
@@ -4,10 +4,10 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
- gem "ruby-prof"
7
+ gem 'pry'
8
+ gem 'ruby-prof', platforms: [:mri]
8
9
  end
9
10
 
10
11
  group :test do
11
- gem 'simplecov'
12
- gem 'coveralls', require: false
12
+ gem 'coveralls', platforms: [:mri]
13
13
  end
data/README.md CHANGED
@@ -7,10 +7,20 @@ generally get the best possible performance.
7
7
  In addition, Trenni includes an SGML/XML builder to assist with the generation
8
8
  of pleasantly formatted markup.
9
9
 
10
- [![Build Status](https://secure.travis-ci.org/ioquatix/trenni.png)](http://travis-ci.org/ioquatix/trenni)
11
- [![Code Climate](https://codeclimate.com/github/ioquatix/trenni.png)](https://codeclimate.com/github/ioquatix/trenni)
10
+ [![Build Status](https://secure.travis-ci.org/ioquatix/trenni.svg)](http://travis-ci.org/ioquatix/trenni)
11
+ [![Code Climate](https://codeclimate.com/github/ioquatix/trenni.svg)](https://codeclimate.com/github/ioquatix/trenni)
12
12
  [![Coverage Status](https://coveralls.io/repos/ioquatix/trenni/badge.svg)](https://coveralls.io/r/ioquatix/trenni)
13
13
 
14
+ ## Motivation
15
+
16
+ Trenni was designed for [Utopia](https://github.com/ioquatix/utopia). When I originally looked at template engines, I was surprised by the level of complexity and the effort involved in processing a template to produce useful output. In particular, many template engines generate an AST and walk over it to generate output (e.g. ERB, at least at the time I last checked). This is exceedingly slow in Ruby.
17
+
18
+ At the time (around 2008?) I was playing around with [ramaze](https://github.com/Ramaze/ramaze) and found a template engine I really liked the design of, called [ezamar](https://github.com/manveru/ezamar). The template compilation process actually generates Ruby code which can then be compiled and executed efficiently. Another engine, by the same author, [nagoro](https://github.com/manveru/nagoro), also provided some inspiration.
19
+
20
+ More recently I was doing some investigation regarding using `eval` for executing the code. The problem is that it's [not possible to replace the binding](http://stackoverflow.com/questions/27391909/replace-evalcode-string-binding-with-lambda/27392437) of a `Proc` once it's created, so template engines that evaluate code in a given binding cannot use a compiled proc, they must parse the code every time. By using a `Proc` we can generate a Ruby function which *can* be compiled to a faster representation by the VM.
21
+
22
+ In addition, I wanted a simple parser and builder for HTML style markup. These are used heavily by Utopia for implementing it's tag based evaluation. The (event based) `Trenni::Parser` is designed so that some day it could be easily written in C. `Trenni::Builder` is a simple and efficient way to generate markup, it's not particularly notable, except that it doesn't use `method_missing` to [implement normal behaviour](https://github.com/sparklemotion/nokogiri/blob/b6679e928924529b56dcc0f3164224c040d14555/lib/nokogiri/xml/builder.rb#L355) which is [sort of slow](http://franck.verrot.fr/blog/2015/07/12/benchmarking-ruby-method-missing-and-define-method/).
23
+
14
24
  ## Installation
15
25
 
16
26
  Add this line to your application's Gemfile:
@@ -62,6 +72,8 @@ Trenni can help construct XML/HTML using a simple DSL:
62
72
 
63
73
  There is a [language-trenni](https://atom.io/packages/language-trenni) package for the [Atom text editor](https://atom.io). It provides syntax highlighting and integration when Trenni is used with the [utopia web framework](https://github.com/ioquatix/utopia).
64
74
 
75
+ [Trenni Formatters](https://github.com/ioquatix/trenni-formatters) is a separate gem that uses `Trenni::Builder` to generate HTML forms easily.
76
+
65
77
  ## Contributing
66
78
 
67
79
  1. Fork it
@@ -74,7 +86,7 @@ There is a [language-trenni](https://atom.io/packages/language-trenni) package f
74
86
 
75
87
  Released under the MIT license.
76
88
 
77
- Copyright, 2012, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
89
+ Copyright, 2012, 2016, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
78
90
 
79
91
  Permission is hereby granted, free of charge, to any person obtaining a copy
80
92
  of this software and associated documentation files (the "Software"), to deal
data/Rakefile CHANGED
@@ -2,7 +2,11 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec) do |task|
5
- task.rspec_opts = ["--require", "simplecov"] if ENV['COVERAGE']
5
+ begin
6
+ require('simplecov/version')
7
+ task.rspec_opts = %w{--require simplecov} if ENV['COVERAGE']
8
+ rescue LoadError
9
+ end
6
10
  end
7
11
 
8
12
  task :default => :spec
@@ -0,0 +1,80 @@
1
+
2
+ require 'benchmark/ips'
3
+ require 'stringio'
4
+
5
+ puts "Ruby #{RUBY_VERSION} at #{Time.now}"
6
+ puts
7
+
8
+ Benchmark.ips do |x|
9
+
10
+ # These two tests look at the cost of simply writing to a buffer.
11
+
12
+ x.report("String (Amortized)") do |i|
13
+ buffer = String.new
14
+
15
+ i.times do
16
+ buffer << "String #{i}"
17
+ end
18
+ end
19
+
20
+ x.report("StringIO (Amortized)") do |i|
21
+ buffer = StringIO.new
22
+
23
+ i.times do
24
+ buffer << "String #{i}"
25
+ end
26
+ end
27
+
28
+ x.compare!
29
+ end
30
+
31
+ # Calculating -------------------------------------
32
+ # String (Amortized) 91666 i/100ms
33
+ # StringIO (Amortized) 69531 i/100ms
34
+ # -------------------------------------------------
35
+ # String (Amortized) 2856024.9 (±8.8%) i/s - 14208230 in 5.017309s
36
+ # StringIO (Amortized) 2424863.3 (±7.0%) i/s - 12098394 in 5.013982s
37
+ #
38
+ # Comparison:
39
+ # String (Amortized): 2856024.9 i/s
40
+ # StringIO (Amortized): 2424863.3 i/s - 1.18x slower
41
+
42
+ # Adjust N to consider the cost of allocation vs the cost of appending.
43
+ N = 5
44
+
45
+ Benchmark.ips do |x|
46
+ # These next two tests consider that multiple writes may be done per buffer allocation.
47
+
48
+ x.report("String") do |i|
49
+ i.times do
50
+ buffer = String.new
51
+
52
+ N.times do
53
+ buffer << "String #{i}"
54
+ end
55
+ end
56
+ end
57
+
58
+ x.report("StringIO") do |i|
59
+ i.times do
60
+ buffer = StringIO.new
61
+
62
+ N.times do
63
+ buffer << "String #{i}"
64
+ end
65
+ end
66
+ end
67
+
68
+ x.compare!
69
+ end
70
+
71
+ # Calculating -------------------------------------
72
+ # String 36822 i/100ms
73
+ # StringIO 32471 i/100ms
74
+ # -------------------------------------------------
75
+ # String 445143.2 (±5.0%) i/s - 2246142 in 5.059017s
76
+ # StringIO 328469.2 (±4.1%) i/s - 1656021 in 5.049919s
77
+ #
78
+ # Comparison:
79
+ # String: 445143.2 i/s
80
+ # StringIO: 328469.2 i/s - 1.36x slower
@@ -18,5 +18,5 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'trenni/builder'
22
- require 'trenni/template'
21
+ require_relative 'trenni/builder'
22
+ require_relative 'trenni/template'
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'trenni/strings'
21
+ require_relative 'strings'
22
22
 
23
23
  module Trenni
24
24
  INSTRUCT_ATTRIBUTES = [
@@ -27,7 +27,7 @@ module Trenni
27
27
  ].freeze
28
28
 
29
29
  class Builder
30
- INDENT = "\t"
30
+ DEFAULT_INDENTATION = "\t".freeze
31
31
 
32
32
  # A helper to generate fragments of markup.
33
33
  def self.fragment(builder = nil, &block)
@@ -40,18 +40,21 @@ module Trenni
40
40
 
41
41
  yield builder
42
42
 
43
- return builder.output.string
43
+ return builder.output
44
44
  end
45
45
  end
46
46
 
47
- def initialize(options = {})
48
- @strict = options[:strict]
47
+ def initialize(strict: false, indent: true, indentation: DEFAULT_INDENTATION, escape: false, output: String.new)
48
+ @strict = strict
49
49
 
50
- @output = options[:output] || StringIO.new
51
- @indentation = options[:indentation] || INDENT
52
- @indent = options.fetch(:indent, true)
50
+ @indent = indent
53
51
 
54
- @escape = options[:escape]
52
+ @indentation = indentation
53
+ # This field gets togged in #inline so we keep track of it separately from @indentation.
54
+
55
+ @escape = escape
56
+
57
+ @output = output
55
58
 
56
59
  @level = [0]
57
60
  @children = [0]
@@ -70,24 +73,11 @@ module Trenni
70
73
  def instruct(attributes = nil)
71
74
  attributes ||= INSTRUCT_ATTRIBUTES
72
75
 
73
- @output.puts "<?xml#{tag_attributes(attributes)}?>"
76
+ @output << "<?xml#{tag_attributes(attributes)}?>\n"
74
77
  end
75
78
 
76
79
  def doctype(attributes = 'html')
77
- if Array === attributes
78
- text = ''
79
- attributes.each do |value|
80
- if value.match(/[\s"]/)
81
- value = '"' + value.gsub('"', '&quot;') + '"'
82
- end
83
-
84
- text += ' ' + value
85
- end
86
- else
87
- text = ' ' + attributes
88
- end
89
-
90
- @output.puts "<!DOCTYPE#{text}>"
80
+ @output << "<!DOCTYPE #{attributes}>\n"
91
81
  end
92
82
 
93
83
  # Begin a block tag.
@@ -112,18 +102,17 @@ module Trenni
112
102
 
113
103
  # Append pre-existing markup:
114
104
  def append(data)
105
+ return unless data
106
+
115
107
  # The parent has one more child:
116
108
  @level[-1] += 1
117
109
 
118
110
  if @indent
119
- lines = data.strip.split(/\n/)
120
-
121
- lines.each_with_index do |line, i|
122
- @output.puts if i > 0
123
- @output.write indentation + line
111
+ data.each_line.with_index do |line, i|
112
+ @output << indentation << line
124
113
  end
125
114
  else
126
- @output.write data
115
+ @output << data
127
116
  end
128
117
  end
129
118
 
@@ -156,12 +145,12 @@ module Trenni
156
145
  def full_tag(name, attributes, indent_outer, indent_inner, &block)
157
146
  if block_given?
158
147
  if indent_outer
159
- @output.puts if @level.last > 0
160
- @output.write indentation
148
+ @output << "\n" if @level.last > 0
149
+ @output << indentation
161
150
  end
162
151
 
163
- @output.write "<#{name}#{tag_attributes(attributes)}>"
164
- @output.puts if indent_inner
152
+ @output << "<#{name}#{tag_attributes(attributes)}>"
153
+ @output << "\n" if indent_inner
165
154
 
166
155
  # The parent has one more child:
167
156
  @level[-1] += 1
@@ -173,16 +162,16 @@ module Trenni
173
162
  children = @level.pop
174
163
 
175
164
  if indent_inner
176
- @output.puts if children > 0
177
- @output.write indentation
165
+ @output << "\n" if children > 0
166
+ @output << indentation
178
167
  end
179
168
 
180
- @output.write "</#{name}>"
169
+ @output << "</#{name}>"
181
170
  else
182
171
  # The parent has one more child:
183
172
  @level[-1] += 1
184
173
 
185
- @output.write indentation + "<#{name}#{tag_attributes(attributes)}/>"
174
+ @output << indentation + "<#{name}#{tag_attributes(attributes)}/>"
186
175
  end
187
176
  end
188
177
  end
@@ -54,7 +54,7 @@ module Trenni
54
54
  end
55
55
 
56
56
  def to_s
57
- "[#{self.line_number}:#{self.line_range}]"
57
+ ":#{self.line_number}"
58
58
  end
59
59
 
60
60
  # The line that contains the @offset (base 0 indexing).
@@ -74,21 +74,6 @@ module Trenni
74
74
  end
75
75
 
76
76
  attr :line_text
77
-
78
- def to_hash
79
- {
80
- :line_number => self.line_number,
81
- :line_offset => self.line_range.min,
82
- :character_offset => self.line_offset,
83
- :text => self.line_text.chomp
84
- }
85
- end
86
- end
87
-
88
- def self.line_at_offset(input, input_offset)
89
- warn "#{self.class}::line_at_offset is deprecated, use Location.new(input, input_offset) directly!"
90
-
91
- Location.new(input, input_offset).to_hash rescue nil
92
77
  end
93
78
 
94
79
  class ParseError < StandardError
@@ -108,13 +93,14 @@ module Trenni
108
93
  @delegate = delegate
109
94
  # The delegate must respond to:
110
95
  # .begin_parse(scanner)
111
- # .text(escaped-data)
112
- # .cdata(unescaped-data)
113
- # .attribute(name, value-or-true)
96
+ # .text(escaped_data)
97
+ # .cdata(unescaped_data)
98
+ # .attribute(name, value_or_true)
114
99
  # .begin_tag(name, :opened or :closed)
115
100
  # .end_tag(begin_tag_type, :opened or :closed)
116
- # .comment(comment-text)
117
- # .instruction(instruction-text)
101
+ # .doctype(doctype_attributes)
102
+ # .comment(comment_text)
103
+ # .instruction(instruction_text)
118
104
  end
119
105
 
120
106
  def parse(string)
@@ -148,8 +134,10 @@ module Trenni
148
134
  scan_tag_normal(scanner, CLOSED_TAG)
149
135
  elsif scanner.scan(/!\[CDATA\[/)
150
136
  scan_tag_cdata(scanner)
151
- elsif scanner.scan(/!/)
137
+ elsif scanner.scan(/!--/)
152
138
  scan_tag_comment(scanner)
139
+ elsif scanner.scan(/!DOCTYPE/)
140
+ scan_doctype(scanner)
153
141
  elsif scanner.scan(/\?/)
154
142
  scan_tag_instruction(scanner)
155
143
  else
@@ -161,9 +149,10 @@ module Trenni
161
149
  def scan_attributes(scanner)
162
150
  # Parse an attribute in the form of key="value" or key.
163
151
  while scanner.scan(/\s*([^\s=\/>]+)/um)
164
- name = scanner[1]
152
+ name = scanner[1].freeze
165
153
  if scanner.scan(/=((['"])(.*?)\2)/um)
166
- @delegate.attribute(name, scanner[3])
154
+ value = scanner[3].freeze
155
+ @delegate.attribute(name, value)
167
156
  else
168
157
  @delegate.attribute(name, true)
169
158
  end
@@ -172,7 +161,7 @@ module Trenni
172
161
 
173
162
  def scan_tag_normal(scanner, begin_tag_type = OPENED_TAG)
174
163
  if scanner.scan(/[^\s\/>]+/)
175
- @delegate.begin_tag(scanner.matched, begin_tag_type)
164
+ @delegate.begin_tag(scanner.matched.freeze, begin_tag_type)
176
165
 
177
166
  scanner.scan(/\s*/)
178
167
  self.scan_attributes(scanner)
@@ -193,34 +182,34 @@ module Trenni
193
182
  raise ParseError.new("Invalid tag!", scanner)
194
183
  end
195
184
  end
196
-
185
+
186
+ def scan_doctype(scanner)
187
+ if scanner.scan_until(/(.*?)>/)
188
+ @delegate.doctype(scanner[1].strip.freeze)
189
+ else
190
+ raise ParseError.new("DOCTYPE is not closed!", scanner)
191
+ end
192
+ end
193
+
197
194
  def scan_tag_cdata(scanner)
198
195
  if scanner.scan_until(/(.*?)\]\]>/m)
199
- @delegate.cdata(scanner[1])
196
+ @delegate.cdata(scanner[1].freeze)
200
197
  else
201
198
  raise ParseError.new("CDATA segment is not closed!", scanner)
202
199
  end
203
200
  end
204
201
 
205
202
  def scan_tag_comment(scanner)
206
- if scanner.scan(/--/)
207
- if scanner.scan_until(/(.*?)-->/m)
208
- @delegate.comment("--" + scanner[1] + "--")
209
- else
210
- raise ParseError.new("Comment is not closed!", scanner)
211
- end
203
+ if scanner.scan_until(/(.*?)-->/m)
204
+ @delegate.comment(scanner[1].freeze)
212
205
  else
213
- if scanner.scan_until(/(.*?)>/)
214
- @delegate.comment(scanner[1])
215
- else
216
- raise ParseError.new("Comment is not closed!", scanner)
217
- end
206
+ raise ParseError.new("Comment is not closed!", scanner)
218
207
  end
219
208
  end
220
209
 
221
210
  def scan_tag_instruction(scanner)
222
211
  if scanner.scan_until(/(.*)\?>/)
223
- @delegate.instruction(scanner[1])
212
+ @delegate.instruction(scanner[1].freeze)
224
213
  end
225
214
  end
226
215
  end
@@ -48,24 +48,26 @@ module Trenni
48
48
 
49
49
  attr :parts
50
50
 
51
+ # Output raw text to the template.
51
52
  def text(text)
52
- text = text.gsub('\\', '\\\\\\').gsub('@', '\\@')
53
-
54
- @parts << "#{OUT} << %q@#{text}@ ; "
53
+ @parts << "#{OUT}<<#{text.dump};"
55
54
  end
56
55
 
56
+ # Output a ruby expression (or part of).
57
57
  def expression(text)
58
- @parts << "#{text} ; "
58
+ @parts << "#{text};"
59
59
  end
60
-
61
- def output(text)
62
- @parts << "#{OUT} << (#{text}) ; "
60
+
61
+ # Output a string interpolation.
62
+ def interpolation(text)
63
+ @parts << "#{OUT}<<(#{text});"
63
64
  end
64
65
 
65
- def code
66
- parts = ["#{OUT} = [] ; "] + @parts + ["#{OUT}"]
66
+ CODE_PREFIX = "#{OUT}=[];".freeze
67
+ CODE_POSTFIX = "#{OUT}".freeze
67
68
 
68
- return parts.join
69
+ def code
70
+ return [CODE_PREFIX, *@parts, CODE_POSTFIX].join
69
71
  end
70
72
  end
71
73
 
@@ -126,7 +128,7 @@ module Trenni
126
128
  end
127
129
 
128
130
  if level == 0
129
- @callback.output(code)
131
+ @callback.interpolation(code)
130
132
  else
131
133
  raise StandardError.new "Could not find end of expression #{self}!"
132
134
  end
@@ -140,16 +142,20 @@ module Trenni
140
142
  end
141
143
  end
142
144
 
143
- def self.load(path)
144
- return self.new(File.read(path), path)
145
+ def self.load_file(path)
146
+ self.new(File.read(path), path)
147
+ end
148
+
149
+ def self.load(io, path = io.inspect)
150
+ self.new(io.read, path)
145
151
  end
146
152
 
147
- def initialize(template, filename = '<Trenni>')
148
- @template = template
149
- @filename = filename
153
+ def initialize(text, path = '<Trenni>')
154
+ @text = text
155
+ @path = path
150
156
  end
151
157
 
152
- def to_string(scope = nil)
158
+ def to_string(scope = Object.new)
153
159
  to_array(scope).join
154
160
  end
155
161
 
@@ -159,15 +165,16 @@ module Trenni
159
165
 
160
166
  def to_array(scope)
161
167
  if Binding === scope
162
- eval(code, scope, @filename)
168
+ # Slow code path, evaluate the code string in the given binding (scope).
169
+ eval(code, scope, @path)
163
170
  else
164
- # This can sometimes be a bit faster:
171
+ # Faster code path, use instance_eval on a compiled Proc.
165
172
  scope.instance_eval(&to_proc)
166
173
  end
167
174
  end
168
175
 
169
176
  def to_proc
170
- @compiled_proc ||= eval("proc{\n#{code}\n}", binding, @filename, 0)
177
+ @compiled_proc ||= eval("proc{\n#{code}\n}", binding, @path, 0)
171
178
  end
172
179
 
173
180
  protected
@@ -178,7 +185,7 @@ module Trenni
178
185
 
179
186
  def compile!
180
187
  buffer = Buffer.new
181
- scanner = Scanner.new(buffer, @template)
188
+ scanner = Scanner.new(buffer, @text)
182
189
 
183
190
  scanner.parse
184
191
 
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Trenni
22
- VERSION = "1.4.5"
22
+ VERSION = "1.5.0"
23
23
  end
@@ -23,20 +23,53 @@
23
23
  require 'trenni'
24
24
 
25
25
  module Trenni::BuilderSpec
26
+ describe 'Trenni::Builder#fragment' do
27
+ let(:builder) {Trenni::Builder.new}
28
+
29
+ it "should use an existing builder" do
30
+ result = Trenni::Builder.fragment do |builder|
31
+ end
32
+
33
+ expect(result).to_not be == nil
34
+ end
35
+
36
+ it "should use an existing builder" do
37
+ expect(Trenni::Builder).to receive(:new).and_call_original
38
+
39
+ result = Trenni::Builder.fragment(builder) do |builder|
40
+ end
41
+
42
+ expect(result).to be == nil
43
+ end
44
+ end
45
+
26
46
  describe Trenni::Builder do
47
+ it 'should be able to append nil' do
48
+ expect{subject.append(nil)}.to_not raise_error(TypeError)
49
+ end
50
+
51
+ it 'should append existing markup' do
52
+ subject.tag("outer") do
53
+ subject.append("<inner>\n\t<nested/>\n</inner>")
54
+ end
55
+
56
+ expect(subject.output).to be == "<outer>\n\t<inner>\n\t\t<nested/>\n\t</inner>\n</outer>"
57
+ end
58
+
27
59
  it "should produce valid xml output" do
28
- builder = Trenni::Builder.new(:indent => false)
60
+ builder = Trenni::Builder.new(indent: false)
29
61
 
30
62
  builder.instruct
31
63
  builder.tag('foo', 'bar' => 'baz') do
32
- builder.text("apples and oranges")
64
+ builder.text('apples and oranges')
65
+ builder.tag('baz')
33
66
  end
34
67
 
35
- expect(builder.output.string).to be == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<foo bar=\"baz\">apples and oranges</foo>"
68
+ expect(builder.output).to be == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<foo bar=\"baz\">apples and oranges<baz/></foo>"
36
69
  end
37
70
 
38
71
  it "should produce valid html" do
39
- builder = Trenni::Builder.new(:indent => true)
72
+ builder = Trenni::Builder.new(indent: true)
40
73
 
41
74
  builder.doctype
42
75
  builder.tag('html') do
@@ -49,7 +82,7 @@ module Trenni::BuilderSpec
49
82
  end
50
83
  end
51
84
 
52
- expect(builder.output.string).to be == "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Hello World</title>\n\t</head>\n\t<body>\n\t</body>\n</html>"
85
+ expect(builder.output).to be == "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Hello World</title>\n\t</head>\n\t<body>\n\t</body>\n</html>"
53
86
  end
54
87
 
55
88
  it "should indent self-closing tag correctly" do
@@ -57,7 +90,7 @@ module Trenni::BuilderSpec
57
90
 
58
91
  builder.tag('foo') { builder.tag('bar') }
59
92
 
60
- expect(builder.output.string).to be == "<foo>\n\t<bar/>\n</foo>"
93
+ expect(builder.output).to be == "<foo>\n\t<bar/>\n</foo>"
61
94
  end
62
95
 
63
96
  it "should produce inline html" do
@@ -71,7 +104,7 @@ module Trenni::BuilderSpec
71
104
  builder.text "World!"
72
105
  end
73
106
 
74
- expect(builder.output.string).to be == "<div><strong>Hello</strong>World!</div>"
107
+ expect(builder.output).to be == "<div><strong>Hello</strong>World!</div>"
75
108
  end
76
109
 
77
110
  it "escapes attributes and text correctly" do
@@ -81,7 +114,7 @@ module Trenni::BuilderSpec
81
114
  builder.text %Q{if x < 10}
82
115
  end
83
116
 
84
- expect(builder.output.string).to be == %Q{<foo bar="&quot;Hello World&quot;">if x &lt; 10</foo>}
117
+ expect(builder.output).to be == %Q{<foo bar="&quot;Hello World&quot;">if x &lt; 10</foo>}
85
118
  end
86
119
 
87
120
  it "should support strict attributes" do
@@ -89,7 +122,7 @@ module Trenni::BuilderSpec
89
122
 
90
123
  builder.tag :option, :required => true
91
124
 
92
- expect(builder.output.string).to be == %Q{<option required="required"/>}
125
+ expect(builder.output).to be == %Q{<option required="required"/>}
93
126
  end
94
127
 
95
128
  it "should support compact attributes" do
@@ -97,7 +130,7 @@ module Trenni::BuilderSpec
97
130
 
98
131
  builder.tag :option, :required => true
99
132
 
100
- expect(builder.output.string).to be == %Q{<option required/>}
133
+ expect(builder.output).to be == %Q{<option required/>}
101
134
  end
102
135
 
103
136
  it "should output without changing escaped characters" do
@@ -105,17 +138,17 @@ module Trenni::BuilderSpec
105
138
 
106
139
  builder.tag "section", :'data-text' => 'foo\nbar'
107
140
 
108
- expect(builder.output.string).to be == '<section data-text="foo\nbar"/>'
141
+ expect(builder.output).to be == '<section data-text="foo\nbar"/>'
109
142
  end
110
143
 
111
144
  it "should order attributes as specified" do
112
145
  builder = Trenni::Builder.new(:strict => true)
113
146
  builder.tag :t, [[:a, 10], [:b, 20]]
114
- expect(builder.output.string).to be == %Q{<t a="10" b="20"/>}
147
+ expect(builder.output).to be == %Q{<t a="10" b="20"/>}
115
148
 
116
149
  builder = Trenni::Builder.new(:strict => true)
117
150
  builder.tag :t, :b => 20, :a => 10
118
- expect(builder.output.string).to be == %Q{<t b="20" a="10"/>}
151
+ expect(builder.output).to be == %Q{<t b="20" a="10"/>}
119
152
  end
120
153
  end
121
154
  end
@@ -40,11 +40,44 @@ module Trenni::ParserSpec
40
40
  end
41
41
 
42
42
  describe Trenni::Parser do
43
+ let(:delegate) {ParserDelegate.new}
44
+ let(:parser) {Trenni::Parser.new(delegate)}
45
+
46
+ it "should parse self-closing tags correctly" do
47
+ parser.parse("<br/>")
48
+
49
+ expect(delegate.events).to be == [
50
+ [:begin_tag, "br", :opened],
51
+ [:finish_tag, :opened, :closed],
52
+ ]
53
+ end
54
+
55
+ it "should parse doctype correctly" do
56
+ parser.parse("<!DOCTYPE html>")
57
+
58
+ expect(delegate.events).to be == [
59
+ [:doctype, "html"]
60
+ ]
61
+ end
62
+
63
+ it "Should parse instruction correctly" do
64
+ parser.parse("<?foo=bar?>")
65
+
66
+ expect(delegate.events).to be == [
67
+ [:instruction, "foo=bar"]
68
+ ]
69
+ end
70
+
71
+ it "should parse comment correctly" do
72
+ parser.parse(%Q{<!--comment-->})
73
+
74
+ expect(delegate.events).to be == [
75
+ [:comment, "comment"]
76
+ ]
77
+ end
78
+
43
79
  it "should parse markup correctly" do
44
- delegate = ParserDelegate.new
45
- scanner = Trenni::Parser.new(delegate)
46
-
47
- scanner.parse(%Q{<foo bar="20" baz>Hello World</foo>})
80
+ parser.parse(%Q{<foo bar="20" baz>Hello World</foo>})
48
81
 
49
82
  expected_events = [
50
83
  [:begin_tag, "foo", :opened],
@@ -60,10 +93,7 @@ module Trenni::ParserSpec
60
93
  end
61
94
 
62
95
  it "should parse CDATA correctly" do
63
- delegate = ParserDelegate.new
64
- scanner = Trenni::Parser.new(delegate)
65
-
66
- scanner.parse(%Q{<test><![CDATA[Hello World]]></test>})
96
+ parser.parse(%Q{<test><![CDATA[Hello World]]></test>})
67
97
 
68
98
  expected_events = [
69
99
  [:begin_tag, "test", :opened],
@@ -77,21 +107,20 @@ module Trenni::ParserSpec
77
107
  end
78
108
 
79
109
  it "should generate errors on incorrect input" do
80
- delegate = ParserDelegate.new
81
- scanner = Trenni::Parser.new(delegate)
82
-
83
- expect{scanner.parse(%Q{<foo})}.to raise_error Trenni::Parser::ParseError
110
+ expect{parser.parse(%Q{<foo})}.to raise_error Trenni::Parser::ParseError
84
111
 
85
- expect{scanner.parse(%Q{<foo bar=>})}.to raise_error Trenni::Parser::ParseError
112
+ expect{parser.parse(%Q{<foo bar=>})}.to raise_error Trenni::Parser::ParseError
86
113
 
87
- expect{scanner.parse(%Q{<foo bar="" baz>})}.to_not raise_error
114
+ expect{parser.parse(%Q{<foo bar="" baz>})}.to_not raise_error
88
115
  end
89
116
 
90
117
  it "should know about line numbers" do
91
118
  data = %Q{Hello\nWorld\nFoo\nBar!}
92
119
 
93
120
  location = Trenni::Parser::Location.new(data, 7)
94
-
121
+
122
+ expect(location.to_i).to be == 7
123
+ expect(location.to_s).to be == ":2"
95
124
  expect(location.line_text).to be == "World"
96
125
 
97
126
  expect(location.line_number).to be == 2
@@ -100,11 +129,8 @@ module Trenni::ParserSpec
100
129
  end
101
130
 
102
131
  it "should know about line numbers when input contains multi-byte characters" do
103
- delegate = ParserDelegate.new
104
- scanner = Trenni::Parser.new(delegate)
105
-
106
132
  data = %Q{<p>\nこんにちは\nWorld\n<p}
107
- error = scanner.parse(data) rescue $!
133
+ error = parser.parse(data) rescue $!
108
134
 
109
135
  expect(error).to be_kind_of Trenni::Parser::ParseError
110
136
  expect(error.location.line_number).to be == 4
@@ -27,6 +27,24 @@ require 'benchmark'
27
27
 
28
28
  module Trenni::TemplateSpec
29
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__)}
43
+
44
+ it "should be able to handle nested interpolations" do
45
+ expect(nested_template.to_string).to be == "Hello world!"
46
+ end
47
+
30
48
  it "should process list of items" do
31
49
  template = Trenni::Template.new('<?r items.each do |item| ?>#{item}<?r end ?>')
32
50
 
@@ -35,7 +53,7 @@ module Trenni::TemplateSpec
35
53
  expect(template.to_string(binding)).to be == "1234"
36
54
  end
37
55
 
38
- let(:large_template) {Trenni::Template.load File.join(__dir__, "large.trenni")}
56
+ let(:large_template) {Trenni::Template.load_file File.expand_path('template_spec/large.trenni', __dir__)}
39
57
 
40
58
  it "should have better performance using instance" do
41
59
  n = 1_000
@@ -54,7 +72,7 @@ module Trenni::TemplateSpec
54
72
  expect(object_time).to be < binding_time
55
73
  end
56
74
 
57
- let(:escaped_template) {Trenni::Template.load File.join(__dir__, "escaped.trenni")}
75
+ let(:escaped_template) {Trenni::Template.load_file File.expand_path('template_spec/escaped.trenni', __dir__)}
58
76
 
59
77
  it "should process escaped characters" do
60
78
  expect(escaped_template.to_string).to be ==
@@ -0,0 +1,8 @@
1
+ <?r
2
+ def append_to_buffer(&block)
3
+ out = Trenni::Template.buffer(block.binding)
4
+ out << "test"
5
+ end
6
+
7
+ append_to_buffer {}
8
+ ?>
@@ -0,0 +1,4 @@
1
+ <?r result = Trenni::Template.capture do ?>
2
+ test test test
3
+ <?r end ?>
4
+ #{result.upcase}
@@ -0,0 +1 @@
1
+ #{{text: "Hello #{'world'}!"}[:text]}
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.4.5
4
+ version: 1.5.0
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-01-12 00:00:00.000000000 Z
11
+ date: 2016-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -69,6 +69,7 @@ files:
69
69
  - Gemfile
70
70
  - README.md
71
71
  - Rakefile
72
+ - benchmark/io_vs_string.rb
72
73
  - lib/trenni.rb
73
74
  - lib/trenni/builder.rb
74
75
  - lib/trenni/parser.rb
@@ -76,11 +77,14 @@ files:
76
77
  - lib/trenni/template.rb
77
78
  - lib/trenni/version.rb
78
79
  - spec/trenni/builder_spec.rb
79
- - spec/trenni/escaped.trenni
80
- - spec/trenni/large.trenni
81
80
  - spec/trenni/parser_spec.rb
82
81
  - spec/trenni/strings_spec.rb
83
82
  - spec/trenni/template_spec.rb
83
+ - spec/trenni/template_spec/buffer.trenni
84
+ - spec/trenni/template_spec/capture.trenni
85
+ - spec/trenni/template_spec/escaped.trenni
86
+ - spec/trenni/template_spec/large.trenni
87
+ - spec/trenni/template_spec/nested.trenni
84
88
  - trenni.gemspec
85
89
  homepage: https://github.com/ioquatix/trenni
86
90
  licenses: []
@@ -101,14 +105,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
105
  version: '0'
102
106
  requirements: []
103
107
  rubyforge_project:
104
- rubygems_version: 2.4.6
108
+ rubygems_version: 2.5.2
105
109
  signing_key:
106
110
  specification_version: 4
107
111
  summary: A fast native templating system that compiles directly to Ruby code.
108
112
  test_files:
109
113
  - spec/trenni/builder_spec.rb
110
- - spec/trenni/escaped.trenni
111
- - spec/trenni/large.trenni
112
114
  - spec/trenni/parser_spec.rb
113
115
  - spec/trenni/strings_spec.rb
114
116
  - spec/trenni/template_spec.rb
117
+ - spec/trenni/template_spec/buffer.trenni
118
+ - spec/trenni/template_spec/capture.trenni
119
+ - spec/trenni/template_spec/escaped.trenni
120
+ - spec/trenni/template_spec/large.trenni
121
+ - spec/trenni/template_spec/nested.trenni