trenni 1.5.1 → 1.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 50784d6ac977a20302a0fbaff7808065161c569c
4
- data.tar.gz: f07903da43893883ab2e5238af72ede943ebe221
3
+ metadata.gz: c17ddcecd402c971c098d70f430380b664cd581e
4
+ data.tar.gz: b1270c75070860bb91008a08c4be7c58f1eaa5e2
5
5
  SHA512:
6
- metadata.gz: 11e041b09097ecb7a0bf3d635ad6d11df0d29b76844e95a920c6e6efd7ec7e87abf4f7fd3013e4e4ce13db7ea69b0d2daf28c45a119a2b1bb0aca91a02a4a5f6
7
- data.tar.gz: df06c19505556ab8b16a82ab4788f6d5f08e782f376f74e53d181d32ca3fa92c1b17f6cbbd843f75f00f00332903cf469e1398eaf0f3f142f6864d0568cb46a3
6
+ metadata.gz: 0c7387728d78797608f8369bd617b86edcaa23bd2663ef1b854917515a09bc0f9888f357267378620af854f469007e5fea9928374477a57025e34c0950fc3efc
7
+ data.tar.gz: 46684a95b5e07c3f522cba2f04db9ca1320c2edcb205d361156014fa9789b6332a014694e8dabb4fe2ce7a32ee56795d4b0be8b1f7dc4a2d7f52f974530bd818
data/README.md CHANGED
@@ -41,13 +41,14 @@ Or install it yourself as:
41
41
 
42
42
  Trenni templates work essentially the same way as all other templating systems:
43
43
 
44
- template = Trenni::Template.new('<?r items.each do |item| ?>#{item}<?r end ?>')
44
+ buffer = Trenni::Buffer.new('<?r items.each do |item| ?>#{item}<?r end ?>')
45
+ template = Trenni::Template.new(buffer)
45
46
 
46
47
  items = 1..4
47
48
 
48
49
  template.to_string(binding) # => "1234"
49
50
 
50
- The code above demonstrate the only two constructs, `<?r expression ?>` and `#{output}`.
51
+ The code above demonstrate the only two constructs, `<?r expression ?>` and `#{output}`.
51
52
 
52
53
  Trenni provides a slightly higher performance API using objects rather than bindings. If you provide an object instance, `instance_eval` would be used instead.
53
54
 
@@ -0,0 +1,79 @@
1
+ # Copyright, 2016, 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
+ module Trenni
22
+ class Buffer
23
+ def initialize(string, path: '<string>')
24
+ @string = string
25
+ @path = path
26
+ end
27
+
28
+ attr :path
29
+
30
+ def read
31
+ @string
32
+ end
33
+
34
+ def self.load_file(path)
35
+ FileBuffer.new(path)
36
+ end
37
+
38
+ def self.load(string)
39
+ Buffer.new(string)
40
+ end
41
+
42
+ def to_buffer
43
+ self
44
+ end
45
+ end
46
+
47
+ class FileBuffer
48
+ def initialize(path)
49
+ @path = path
50
+ end
51
+
52
+ attr :path
53
+
54
+ def read
55
+ @buffer ||= File.read(@path)
56
+ end
57
+
58
+ def to_buffer
59
+ Buffer.new(self.read, @path)
60
+ end
61
+ end
62
+
63
+ class IOBuffer
64
+ def initialize(io, path: io.inspect)
65
+ @io = io
66
+ @path = path
67
+ end
68
+
69
+ attr :path
70
+
71
+ def read
72
+ @io.read
73
+ end
74
+
75
+ def to_buffer
76
+ Buffer.new(self.read, path: @path)
77
+ end
78
+ end
79
+ end
data/lib/trenni/parser.rb CHANGED
@@ -18,81 +18,21 @@
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 'strscan'
21
+ require_relative 'scanner'
22
22
 
23
23
  module Trenni
24
24
  # This parser processes general markup into a sequence of events which are passed to a delegate.
25
- class Parser
25
+ class Parser < StringScanner
26
26
  OPENED_TAG = :opened
27
27
  CLOSED_TAG = :closed
28
28
 
29
- class Location
30
- def initialize(input, offset)
31
- raise ArgumentError.new("Offset #{index} is past end of input #{input.bytesize}") if offset > input.bytesize
32
-
33
- @offset = offset
34
- @line_index = 0
35
- line_offset = next_line_offset = 0
36
-
37
- input.each_line do |line|
38
- line_offset = next_line_offset
39
- next_line_offset += line.bytesize
40
-
41
- # Is our input offset within this line?
42
- if next_line_offset >= offset
43
- @line_text = line.chomp
44
- @line_range = line_offset...next_line_offset
45
- break
46
- else
47
- @line_index += 1
48
- end
49
- end
50
- end
51
-
52
- def to_i
53
- @offset
54
- end
55
-
56
- def to_s
57
- ":#{self.line_number}"
58
- end
59
-
60
- # The line that contains the @offset (base 0 indexing).
61
- attr :line_index
62
-
63
- # The line index, but base-1.
64
- def line_number
65
- @line_index + 1
66
- end
29
+ def initialize(buffer, delegate)
30
+ super(buffer)
67
31
 
68
- # The byte offset to the start of that line.
69
- attr :line_range
70
-
71
- # The number of bytes from the start of the line to the given offset in the input.
72
- def line_offset
73
- @offset - @line_range.min
74
- end
75
-
76
- attr :line_text
77
- end
78
-
79
- class ParseError < StandardError
80
- def initialize(message, scanner)
81
- @message = message
82
- @location = Location.new(scanner.string, scanner.pos)
83
- end
84
-
85
- attr :location
86
-
87
- def to_s
88
- "#{@message} at #{@location}"
89
- end
90
- end
91
-
92
- def initialize(delegate)
93
32
  @delegate = delegate
33
+
94
34
  # The delegate must respond to:
95
- # .begin_parse(scanner)
35
+ # .begin_parse(self)
96
36
  # .text(escaped_data)
97
37
  # .cdata(unescaped_data)
98
38
  # .attribute(name, value_or_true)
@@ -103,55 +43,52 @@ module Trenni
103
43
  # .instruction(instruction_text)
104
44
  end
105
45
 
106
- def parse(string)
107
- scanner = StringScanner.new(string)
108
- @delegate.begin_parse(scanner)
46
+ def parse!
47
+ @delegate.begin_parse(self)
109
48
 
110
- until scanner.eos?
111
- start_pos = scanner.pos
49
+ until eos?
50
+ start_pos = self.pos
112
51
 
113
- scan_text(scanner)
114
- scan_tag(scanner)
52
+ scan_text
53
+ scan_tag
115
54
 
116
- if start_pos == scanner.pos
117
- raise ParseError.new("Scanner didn't move", scanner)
118
- end
55
+ raise_if_stuck(start_pos)
119
56
  end
120
57
  end
121
58
 
122
59
  protected
123
60
 
124
- def scan_text(scanner)
61
+ def scan_text
125
62
  # Match any character data except the open tag character.
126
- if scanner.scan(/[^<]+/m)
127
- @delegate.text(scanner.matched)
63
+ if self.scan(/[^<]+/m)
64
+ @delegate.text(self.matched)
128
65
  end
129
66
  end
130
67
 
131
- def scan_tag(scanner)
132
- if scanner.scan(/</)
133
- if scanner.scan(/\//)
134
- scan_tag_normal(scanner, CLOSED_TAG)
135
- elsif scanner.scan(/!\[CDATA\[/)
136
- scan_tag_cdata(scanner)
137
- elsif scanner.scan(/!--/)
138
- scan_tag_comment(scanner)
139
- elsif scanner.scan(/!DOCTYPE/)
140
- scan_doctype(scanner)
141
- elsif scanner.scan(/\?/)
142
- scan_tag_instruction(scanner)
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
143
80
  else
144
- scan_tag_normal(scanner)
81
+ scan_tag_normal
145
82
  end
146
83
  end
147
84
  end
148
85
 
149
- def scan_attributes(scanner)
86
+ def scan_attributes
150
87
  # Parse an attribute in the form of key="value" or key.
151
- while scanner.scan(/\s*([^\s=\/>]+)/um)
152
- name = scanner[1].freeze
153
- if scanner.scan(/=((['"])(.*?)\2)/um)
154
- value = scanner[3].freeze
88
+ while self.scan(/\s*([^\s=\/>]+)/um)
89
+ name = self[1].freeze
90
+ if self.scan(/=((['"])(.*?)\2)/um)
91
+ value = self[3].freeze
155
92
  @delegate.attribute(name, value)
156
93
  else
157
94
  @delegate.attribute(name, true)
@@ -159,57 +96,57 @@ module Trenni
159
96
  end
160
97
  end
161
98
 
162
- def scan_tag_normal(scanner, begin_tag_type = OPENED_TAG)
163
- if scanner.scan(/[^\s\/>]+/)
164
- @delegate.begin_tag(scanner.matched.freeze, begin_tag_type)
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)
165
102
 
166
- scanner.scan(/\s*/)
167
- self.scan_attributes(scanner)
168
- scanner.scan(/\s*/)
103
+ self.scan(/\s*/)
104
+ self.scan_attributes
105
+ self.scan(/\s*/)
169
106
 
170
- if scanner.scan(/\/>/)
107
+ if self.scan(/\/>/)
171
108
  if begin_tag_type == CLOSED_TAG
172
- raise ParseError.new("Tag cannot be closed at both ends!", scanner)
109
+ parse_error!("Tag cannot be closed at both ends!")
173
110
  else
174
111
  @delegate.finish_tag(begin_tag_type, CLOSED_TAG)
175
112
  end
176
- elsif scanner.scan(/>/)
113
+ elsif self.scan(/>/)
177
114
  @delegate.finish_tag(begin_tag_type, OPENED_TAG)
178
115
  else
179
- raise ParseError.new("Invalid characters in tag!", scanner)
116
+ parse_error!("Invalid characters in tag!")
180
117
  end
181
118
  else
182
- raise ParseError.new("Invalid tag!", scanner)
119
+ parse_error!("Invalid tag!")
183
120
  end
184
121
  end
185
122
 
186
- def scan_doctype(scanner)
187
- if scanner.scan_until(/(.*?)>/)
188
- @delegate.doctype(scanner[1].strip.freeze)
123
+ def scan_doctype
124
+ if self.scan_until(/(.*?)>/)
125
+ @delegate.doctype(self[1].strip.freeze)
189
126
  else
190
- raise ParseError.new("DOCTYPE is not closed!", scanner)
127
+ parse_error!("DOCTYPE is not closed!")
191
128
  end
192
129
  end
193
130
 
194
- def scan_tag_cdata(scanner)
195
- if scanner.scan_until(/(.*?)\]\]>/m)
196
- @delegate.cdata(scanner[1].freeze)
131
+ def scan_tag_cdata
132
+ if self.scan_until(/(.*?)\]\]>/m)
133
+ @delegate.cdata(self[1].freeze)
197
134
  else
198
- raise ParseError.new("CDATA segment is not closed!", scanner)
135
+ parse_error!("CDATA segment is not closed!")
199
136
  end
200
137
  end
201
138
 
202
- def scan_tag_comment(scanner)
203
- if scanner.scan_until(/(.*?)-->/m)
204
- @delegate.comment(scanner[1].freeze)
139
+ def scan_tag_comment
140
+ if self.scan_until(/(.*?)-->/m)
141
+ @delegate.comment(self[1].freeze)
205
142
  else
206
- raise ParseError.new("Comment is not closed!", scanner)
143
+ parse_error!("Comment is not closed!")
207
144
  end
208
145
  end
209
146
 
210
- def scan_tag_instruction(scanner)
211
- if scanner.scan_until(/(.*)\?>/)
212
- @delegate.instruction(scanner[1].freeze)
147
+ def scan_tag_instruction
148
+ if self.scan_until(/(.*)\?>/)
149
+ @delegate.instruction(self[1].freeze)
213
150
  end
214
151
  end
215
152
  end
@@ -0,0 +1,138 @@
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 'buffer'
22
+
23
+ require 'strscan'
24
+
25
+ module Trenni
26
+ class ParseError < StandardError
27
+ def initialize(message, scanner, positions = nil)
28
+ super(message)
29
+
30
+ @path = scanner.path
31
+
32
+ @locations = []
33
+
34
+ if positions
35
+ positions.each do |position|
36
+ @locations << Location.new(scanner.string, position)
37
+ end
38
+ else
39
+ @locations = [Location.new(scanner.string, scanner.pos)]
40
+ end
41
+
42
+ @location = @locations.first
43
+
44
+ @input_name = nil
45
+ end
46
+
47
+ attr :locations
48
+ attr :location
49
+
50
+ attr :path
51
+
52
+ def to_s
53
+ "#{@path}#{@location}: #{super}\n#{location.line_text}"
54
+ end
55
+ end
56
+
57
+ class Location
58
+ def initialize(input, offset)
59
+ raise ArgumentError.new("Offset #{index} is past end of input #{input.bytesize}") if offset > input.bytesize
60
+
61
+ @offset = offset
62
+ @line_index = 0
63
+ line_offset = next_line_offset = 0
64
+
65
+ input.each_line do |line|
66
+ line_offset = next_line_offset
67
+ next_line_offset += line.bytesize
68
+
69
+ # Is our input offset within this line?
70
+ if next_line_offset >= offset
71
+ @line_text = line.chomp
72
+ @line_range = line_offset...next_line_offset
73
+ break
74
+ else
75
+ @line_index += 1
76
+ end
77
+ end
78
+ end
79
+
80
+ def to_i
81
+ @offset
82
+ end
83
+
84
+ def to_s
85
+ "[#{self.line_number}]"
86
+ end
87
+
88
+ # The line that contains the @offset (base 0 indexing).
89
+ attr :line_index
90
+
91
+ # The line index, but base-1.
92
+ def line_number
93
+ @line_index + 1
94
+ end
95
+
96
+ # The byte offset to the start of that line.
97
+ attr :line_range
98
+
99
+ # The number of bytes from the start of the line to the given offset in the input.
100
+ def line_offset
101
+ @offset - @line_range.min
102
+ end
103
+
104
+ attr :line_text
105
+ end
106
+
107
+ class StringScanner < ::StringScanner
108
+ def initialize(buffer)
109
+ @buffer = buffer
110
+
111
+ super(buffer.read)
112
+ end
113
+
114
+ attr :buffer
115
+
116
+ def path
117
+ @buffer.path
118
+ end
119
+
120
+ STUCK_MESSAGE = "Parser is stuck!".freeze
121
+
122
+ def stuck?(position)
123
+ self.pos == position
124
+ end
125
+
126
+ def raise_if_stuck(position, message = STUCK_MESSAGE)
127
+ if stuck?(position)
128
+ parse_error!(message)
129
+ end
130
+ end
131
+
132
+ def parse_error!(message, positions = nil)
133
+ positions ||= [self.pos]
134
+
135
+ raise ParseError.new(message, self, positions)
136
+ end
137
+ end
138
+ end
@@ -18,8 +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 'strscan'
22
- require 'stringio'
21
+ require_relative 'scanner'
23
22
 
24
23
  module Trenni
25
24
  # The output variable that will be used in templates:
@@ -41,7 +40,7 @@ module Trenni
41
40
  eval(OUT, binding)
42
41
  end
43
42
 
44
- class Buffer
43
+ class Assembler
45
44
  def initialize
46
45
  @parts = []
47
46
  end
@@ -76,92 +75,109 @@ module Trenni
76
75
  end
77
76
 
78
77
  class Scanner < StringScanner
79
- TEXT = /([^<#]|<(?!\?r)|#(?!\{)){1,1024}/m
80
-
81
- def initialize(callback, string)
82
- @callback = callback
83
- super(string)
78
+ def initialize(buffer, delegate)
79
+ super(buffer)
80
+
81
+ @delegate = delegate
84
82
  end
85
-
86
- def parse
83
+
84
+ def parse!
87
85
  until eos?
88
- pos = self.pos
86
+ start_pos = self.pos
89
87
 
90
88
  scan_text
91
- scan_expression
92
-
93
- if pos == self.pos
94
- raise StandardError.new "Could not scan current input #{self.pos} #{eos?}!"
95
- end
89
+ scan_expression or scan_interpolation
90
+
91
+ raise_if_stuck(start_pos)
96
92
  end
97
93
  end
98
94
 
95
+ # This is formulated specifically so that it matches up until the start of a code block.
96
+ TEXT = /([^<#]|<(?!\?r)|#(?!\{)){1,}/m
97
+
99
98
  def scan_text
100
99
  if scan(TEXT)
101
- @callback.text(matched)
100
+ @delegate.text(self.matched)
102
101
  end
103
102
  end
104
103
 
105
104
  def scan_expression
105
+ start_pos = self.pos
106
+
107
+ if scan(/<\?r/)
108
+ if scan_until(/(.*?)\?>/m)
109
+ @delegate.expression(self[1])
110
+ else
111
+ parse_error!("Could not find end of expression!", [start_pos, self.pos])
112
+ end
113
+
114
+ return true
115
+ end
116
+
117
+ return false
118
+ end
119
+
120
+ def scan_interpolation
121
+ start_pos = self.pos
122
+
106
123
  if scan(/\#\{/)
107
124
  level = 1
108
- code = ""
125
+ code = String.new
109
126
 
110
- until eos? || level == 0
127
+ until eos?
128
+ current_pos = self.pos
129
+
130
+ # Scan anything other than something which causes nesting:
111
131
  if scan(/[^"'\{\}]+/m)
112
132
  code << matched
113
133
  end
114
-
115
- if scan(/"(\\"|[^"])*"/m)
116
- code << matched
117
- end
118
-
119
- if scan(/'(\\'|[^'])*'/m)
134
+
135
+ # Scan a quoted string:
136
+ if scan(/'(\\'|[^'])*'/m) or scan(/"(\\"|[^"])*"/m)
120
137
  code << matched
121
138
  end
122
-
139
+
140
+ # Scan something which nests:
123
141
  if scan(/\{/)
124
142
  code << matched
125
143
  level += 1
126
144
  end
127
145
 
128
146
  if scan(/\}/)
129
- code << matched if level > 1
130
147
  level -= 1
148
+ if level == 0
149
+ @delegate.interpolation(code)
150
+ return true
151
+ else
152
+ code << matched
153
+ end
131
154
  end
155
+
156
+ break if stuck?(current_pos)
132
157
  end
133
-
134
- if level == 0
135
- @callback.interpolation(code)
136
- else
137
- raise StandardError.new "Could not find end of expression #{self}!"
138
- end
139
- elsif scan(/<\?r/)
140
- if scan_until(/(.*?)\?>/m)
141
- @callback.expression(self[1])
142
- else
143
- raise StandardError.new "Could not find end of expression #{self}!"
144
- end
158
+
159
+ parse_error!("Could not find end of interpolation!", [start_pos, self.pos])
145
160
  end
161
+
162
+ return false
146
163
  end
147
164
  end
148
165
 
149
166
  def self.load_file(path)
150
- self.new(File.read(path), path)
167
+ self.new(FileBuffer.new(path))
151
168
  end
152
169
 
153
- def self.load(io, path = io.inspect)
154
- self.new(io.read, path)
155
- end
156
-
157
- def initialize(text, path = '<Trenni>')
158
- @text = text
159
- @path = path
170
+ def initialize(buffer)
171
+ @buffer = buffer
160
172
  end
161
173
 
162
174
  def to_string(scope = Object.new)
163
175
  to_array(scope).join
164
176
  end
177
+
178
+ def to_buffer(scope)
179
+ Buffer.new(to_array(scope).join, path: @buffer.path)
180
+ end
165
181
 
166
182
  # Legacy functions:
167
183
  alias evaluate to_string
@@ -170,7 +186,7 @@ module Trenni
170
186
  def to_array(scope)
171
187
  if Binding === scope
172
188
  # Slow code path, evaluate the code string in the given binding (scope).
173
- eval(code, scope, @path)
189
+ eval(code, scope, @buffer.path)
174
190
  else
175
191
  # Faster code path, use instance_eval on a compiled Proc.
176
192
  scope.instance_eval(&to_proc)
@@ -178,7 +194,7 @@ module Trenni
178
194
  end
179
195
 
180
196
  def to_proc
181
- @compiled_proc ||= eval("proc{\n#{code}\n}", binding, @path, 0)
197
+ @compiled_proc ||= eval("proc{;#{code};}", binding, @buffer.path)
182
198
  end
183
199
 
184
200
  protected
@@ -188,12 +204,11 @@ module Trenni
188
204
  end
189
205
 
190
206
  def compile!
191
- buffer = Buffer.new
192
- scanner = Scanner.new(buffer, @text)
207
+ assembler = Assembler.new
193
208
 
194
- scanner.parse
209
+ Scanner.new(@buffer, assembler).parse!
195
210
 
196
- buffer.code
211
+ assembler.code
197
212
  end
198
213
  end
199
214
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Trenni
22
- VERSION = "1.5.1"
22
+ VERSION = "1.6.0"
23
23
  end
@@ -40,11 +40,16 @@ 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)}
43
+ def parse(input)
44
+ delegate = ParserDelegate.new
45
+ buffer = Trenni::Buffer.new(input)
46
+ Trenni::Parser.new(buffer, delegate).parse!
47
+
48
+ return delegate
49
+ end
45
50
 
46
51
  it "should parse self-closing tags correctly" do
47
- parser.parse("<br/>")
52
+ delegate = parse("<br/>")
48
53
 
49
54
  expect(delegate.events).to be == [
50
55
  [:begin_tag, "br", :opened],
@@ -53,7 +58,7 @@ module Trenni::ParserSpec
53
58
  end
54
59
 
55
60
  it "should parse doctype correctly" do
56
- parser.parse("<!DOCTYPE html>")
61
+ delegate = parse("<!DOCTYPE html>")
57
62
 
58
63
  expect(delegate.events).to be == [
59
64
  [:doctype, "html"]
@@ -61,7 +66,7 @@ module Trenni::ParserSpec
61
66
  end
62
67
 
63
68
  it "Should parse instruction correctly" do
64
- parser.parse("<?foo=bar?>")
69
+ delegate = parse("<?foo=bar?>")
65
70
 
66
71
  expect(delegate.events).to be == [
67
72
  [:instruction, "foo=bar"]
@@ -69,7 +74,7 @@ module Trenni::ParserSpec
69
74
  end
70
75
 
71
76
  it "should parse comment correctly" do
72
- parser.parse(%Q{<!--comment-->})
77
+ delegate = parse(%Q{<!--comment-->})
73
78
 
74
79
  expect(delegate.events).to be == [
75
80
  [:comment, "comment"]
@@ -77,7 +82,7 @@ module Trenni::ParserSpec
77
82
  end
78
83
 
79
84
  it "should parse markup correctly" do
80
- parser.parse(%Q{<foo bar="20" baz>Hello World</foo>})
85
+ delegate = parse(%Q{<foo bar="20" baz>Hello World</foo>})
81
86
 
82
87
  expected_events = [
83
88
  [:begin_tag, "foo", :opened],
@@ -93,7 +98,7 @@ module Trenni::ParserSpec
93
98
  end
94
99
 
95
100
  it "should parse CDATA correctly" do
96
- parser.parse(%Q{<test><![CDATA[Hello World]]></test>})
101
+ delegate = parse(%Q{<test><![CDATA[Hello World]]></test>})
97
102
 
98
103
  expected_events = [
99
104
  [:begin_tag, "test", :opened],
@@ -107,20 +112,20 @@ module Trenni::ParserSpec
107
112
  end
108
113
 
109
114
  it "should generate errors on incorrect input" do
110
- expect{parser.parse(%Q{<foo})}.to raise_error Trenni::Parser::ParseError
115
+ expect{parse(%Q{<foo})}.to raise_error Trenni::ParseError
111
116
 
112
- expect{parser.parse(%Q{<foo bar=>})}.to raise_error Trenni::Parser::ParseError
117
+ expect{parse(%Q{<foo bar=>})}.to raise_error Trenni::ParseError
113
118
 
114
- expect{parser.parse(%Q{<foo bar="" baz>})}.to_not raise_error
119
+ expect{parse(%Q{<foo bar="" baz>})}.to_not raise_error
115
120
  end
116
121
 
117
122
  it "should know about line numbers" do
118
123
  data = %Q{Hello\nWorld\nFoo\nBar!}
119
124
 
120
- location = Trenni::Parser::Location.new(data, 7)
125
+ location = Trenni::Location.new(data, 7)
121
126
 
122
127
  expect(location.to_i).to be == 7
123
- expect(location.to_s).to be == ":2"
128
+ expect(location.to_s).to be == "[2]"
124
129
  expect(location.line_text).to be == "World"
125
130
 
126
131
  expect(location.line_number).to be == 2
@@ -130,9 +135,9 @@ module Trenni::ParserSpec
130
135
 
131
136
  it "should know about line numbers when input contains multi-byte characters" do
132
137
  data = %Q{<p>\nこんにちは\nWorld\n<p}
133
- error = parser.parse(data) rescue $!
138
+ error = parse(data) rescue $!
134
139
 
135
- expect(error).to be_kind_of Trenni::Parser::ParseError
140
+ expect(error).to be_kind_of Trenni::ParseError
136
141
  expect(error.location.line_number).to be == 4
137
142
  end
138
143
  end
@@ -46,7 +46,8 @@ module Trenni::TemplateSpec
46
46
  end
47
47
 
48
48
  it "should process list of items" do
49
- template = Trenni::Template.new('<?r items.each do |item| ?>#{item}<?r end ?>')
49
+ buffer = Trenni::Buffer.new('<?r items.each do |item| ?>#{item}<?r end ?>')
50
+ template = Trenni::Template.new(buffer)
50
51
 
51
52
  items = 1..4
52
53
 
@@ -83,5 +84,14 @@ module Trenni::TemplateSpec
83
84
  "This\\nisn't one line.\n" +
84
85
  "\\tIndentation is the best."
85
86
  end
87
+
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
86
96
  end
87
97
  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.5.1
4
+ version: 1.6.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-03-11 00:00:00.000000000 Z
11
+ date: 2016-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,8 +71,10 @@ files:
71
71
  - Rakefile
72
72
  - benchmark/io_vs_string.rb
73
73
  - lib/trenni.rb
74
+ - lib/trenni/buffer.rb
74
75
  - lib/trenni/builder.rb
75
76
  - lib/trenni/parser.rb
77
+ - lib/trenni/scanner.rb
76
78
  - lib/trenni/strings.rb
77
79
  - lib/trenni/template.rb
78
80
  - lib/trenni/version.rb