trenni 1.5.1 → 1.6.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: 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