slim 0.6.1 → 0.7.0.beta.2
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.
- data/.gitignore +8 -0
- data/Gemfile +1 -6
- data/Gemfile.lock +20 -16
- data/README.md +137 -90
- data/Rakefile +9 -37
- data/benchmarks/run.rb +63 -0
- data/benchmarks/src/complex.erb +23 -0
- data/benchmarks/src/complex.haml +18 -0
- data/benchmarks/src/complex.slim +18 -0
- data/benchmarks/src/complex_view.rb +17 -0
- data/lib/slim.rb +15 -11
- data/lib/slim/compiler.rb +71 -137
- data/lib/slim/embedded_engine.rb +108 -0
- data/lib/slim/end_inserter.rb +57 -0
- data/lib/slim/engine.rb +16 -14
- data/lib/slim/filter.rb +44 -0
- data/lib/slim/helpers.rb +37 -0
- data/lib/slim/parser.rb +355 -0
- data/lib/slim/rails.rb +2 -2
- data/lib/slim/template.rb +18 -0
- data/lib/slim/version.rb +3 -0
- data/slim.gemspec +26 -66
- data/test/helper.rb +32 -4
- data/test/slim/test_code_blocks.rb +33 -0
- data/test/slim/test_code_escaping.rb +69 -0
- data/test/slim/test_code_evaluation.rb +199 -0
- data/test/slim/test_code_helpers.rb +12 -0
- data/test/slim/test_code_output.rb +116 -0
- data/test/slim/test_code_structure.rb +84 -0
- data/test/slim/test_embedded_engines.rb +55 -0
- data/test/slim/test_html_escaping.rb +32 -0
- data/test/slim/test_html_structure.rb +181 -0
- data/test/slim/test_parser_errors.rb +98 -0
- data/test/slim/test_slim_template.rb +128 -0
- data/vim/slim.vim +33 -0
- data/vim/test.slim +27 -0
- metadata +127 -34
- data/lib/slim/optimizer.rb +0 -70
- data/readme.html +0 -159
- data/test/slim/test_compiler.rb +0 -389
- data/test/slim/test_engine.rb +0 -458
- data/test/test_slim.rb +0 -4
@@ -0,0 +1,57 @@
|
|
1
|
+
module Slim
|
2
|
+
# In Slim you don't need to close any blocks:
|
3
|
+
#
|
4
|
+
# - if Slim.awesome?
|
5
|
+
# | But of course it is!
|
6
|
+
#
|
7
|
+
# However, the parser is not smart enough (and that's a good thing) to
|
8
|
+
# automatically insert end's where they are needed. Luckily, this filter
|
9
|
+
# does *exactly* that (and it does it well!)
|
10
|
+
class EndInserter < Filter
|
11
|
+
ELSE_REGEX = /^(else|elsif|when|end)\b/
|
12
|
+
END_REGEX = /^end\b/
|
13
|
+
|
14
|
+
def on_multi(*exps)
|
15
|
+
result = [:multi]
|
16
|
+
# This variable is true if the previous line was
|
17
|
+
# (1) a control code and (2) contained indented content.
|
18
|
+
prev_indent = false
|
19
|
+
|
20
|
+
exps.each do |exp|
|
21
|
+
if control?(exp)
|
22
|
+
if prev_indent
|
23
|
+
# Two control code in a row. If this one is *not*
|
24
|
+
# an else block, we should close the previous one.
|
25
|
+
append_end(result) if exp[2] !~ ELSE_REGEX
|
26
|
+
prev_indent = exp[2].match(END_REGEX).nil?
|
27
|
+
else
|
28
|
+
# Indent if the control code contains something.
|
29
|
+
prev_indent = !empty_exp?(exp[3])
|
30
|
+
end
|
31
|
+
elsif exp[0] != :newline && prev_indent
|
32
|
+
# This is *not* a control code, so we should close the previous one.
|
33
|
+
# Ignores newlines because they will be inserted after each line.
|
34
|
+
append_end(result)
|
35
|
+
prev_indent = false
|
36
|
+
end
|
37
|
+
|
38
|
+
result << compile(exp)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The last line can be a control code too.
|
42
|
+
prev_indent ? append_end(result) : result
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Appends an end.
|
48
|
+
def append_end(result)
|
49
|
+
result << [:block, 'end']
|
50
|
+
end
|
51
|
+
|
52
|
+
# Checks if an expression is a Slim control code.
|
53
|
+
def control?(exp)
|
54
|
+
exp[0] == :slim && exp[1] == :control
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/slim/engine.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
module Slim
|
2
|
-
class Engine
|
3
|
-
|
2
|
+
class Engine < Temple::Engine
|
3
|
+
use Slim::Parser
|
4
|
+
use Slim::EndInserter
|
5
|
+
use Slim::Compiler, :use_html_safe
|
6
|
+
#use Slim::Debugger
|
7
|
+
use Temple::HTML::Fast, :format, :attr_wrapper => '"', :format => :html5
|
8
|
+
filter :MultiFlattener
|
9
|
+
filter :StaticMerger
|
10
|
+
filter :DynamicInliner
|
11
|
+
generator :ArrayBuffer
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@template = template
|
12
|
-
compile
|
13
|
-
end
|
14
|
-
|
15
|
-
def render(scope = Object.new, locals = {})
|
16
|
-
scope.instance_eval(optimized)
|
13
|
+
def self.new(*args)
|
14
|
+
if args.first.respond_to?(:each_line)
|
15
|
+
Template.new(Hash === args.last ? args.last : {}) { args.first }
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/slim/filter.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Slim
|
2
|
+
class Filter
|
3
|
+
include Temple::Utils
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {}
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile(exp)
|
12
|
+
if exp[0] == :slim
|
13
|
+
_, type, *args = exp
|
14
|
+
else
|
15
|
+
type, *args = exp
|
16
|
+
end
|
17
|
+
|
18
|
+
if respond_to?("on_#{type}")
|
19
|
+
send("on_#{type}", *args)
|
20
|
+
else
|
21
|
+
exp
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_control(code, content)
|
26
|
+
[:slim, :control, code, compile(content)]
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_tag(name, attrs, content)
|
30
|
+
[:slim, :tag, name, attrs, compile(content)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_multi(*exps)
|
34
|
+
[:multi, *exps.map { |exp| compile(exp) }]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Debugger < Filter
|
39
|
+
def compile(exp)
|
40
|
+
puts exp.inspect
|
41
|
+
exp
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/slim/helpers.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Slim
|
2
|
+
module Helpers
|
3
|
+
def list_of(enum, &block)
|
4
|
+
enum.map do |i|
|
5
|
+
"<li>#{yield(i)}</li>"
|
6
|
+
end.join("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def escape_html_safe(html)
|
10
|
+
html.html_safe? ? html : escape_html(html)
|
11
|
+
end
|
12
|
+
|
13
|
+
if defined?(EscapeUtils)
|
14
|
+
def escape_html(html)
|
15
|
+
EscapeUtils.escape_html(html.to_s)
|
16
|
+
end
|
17
|
+
elsif RUBY_VERSION > '1.9'
|
18
|
+
ESCAPE_HTML = {
|
19
|
+
'&' => '&',
|
20
|
+
'"' => '"',
|
21
|
+
'<' => '<',
|
22
|
+
'>' => '>',
|
23
|
+
'/' => '/',
|
24
|
+
}
|
25
|
+
|
26
|
+
def escape_html(html)
|
27
|
+
html.to_s.gsub(/[&\"<>\/]/, ESCAPE_HTML)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def escape_html(html)
|
31
|
+
html.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<').gsub(/\//, '/')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module_function :escape_html, :escape_html_safe
|
36
|
+
end
|
37
|
+
end
|
data/lib/slim/parser.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
module Slim
|
2
|
+
class Parser
|
3
|
+
class SyntaxError < StandardError
|
4
|
+
def initialize(message, line, lineno, column = 0)
|
5
|
+
@message = message
|
6
|
+
@line = line.strip
|
7
|
+
@lineno = lineno
|
8
|
+
@column = column
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
%{#{@message}
|
13
|
+
Line #{@lineno}
|
14
|
+
#{@line}
|
15
|
+
#{' ' * @column}^
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
@options = options
|
24
|
+
@tab = ' ' * (options[:tabsize] || 4)
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile(str)
|
28
|
+
lineno = 0
|
29
|
+
result = [:multi]
|
30
|
+
|
31
|
+
# Since you can indent however you like in Slim, we need to keep a list
|
32
|
+
# of how deeply indented you are. For instance, in a template like this:
|
33
|
+
#
|
34
|
+
# ! doctype # 0 spaces
|
35
|
+
# html # 0 spaces
|
36
|
+
# head # 1 space
|
37
|
+
# title # 4 spaces
|
38
|
+
#
|
39
|
+
# indents will then contain [0, 1, 4] (when it's processing the last line.)
|
40
|
+
#
|
41
|
+
# We uses this information to figure out how many steps we must "jump"
|
42
|
+
# out when we see an de-indented line.
|
43
|
+
indents = [0]
|
44
|
+
|
45
|
+
# Whenever we want to output something, we'll *always* output it to the
|
46
|
+
# last stack in this array. So when there's a line that expects
|
47
|
+
# indentation, we simply push a new stack onto this array. When it
|
48
|
+
# processes the next line, the content will then be outputted into that
|
49
|
+
# stack.
|
50
|
+
stacks = [result]
|
51
|
+
|
52
|
+
# String buffer used for broken line (Lines ending with \)
|
53
|
+
broken_line = nil
|
54
|
+
|
55
|
+
# We have special treatment for text blocks:
|
56
|
+
#
|
57
|
+
# |
|
58
|
+
# Hello
|
59
|
+
# World!
|
60
|
+
#
|
61
|
+
text_indent, text_base_indent = nil, nil
|
62
|
+
|
63
|
+
str.each_line do |line|
|
64
|
+
lineno += 1
|
65
|
+
|
66
|
+
# Remove the newline at the ned
|
67
|
+
line.chop!
|
68
|
+
|
69
|
+
# Handle broken lines
|
70
|
+
if broken_line
|
71
|
+
if broken_line[-1] == ?\\
|
72
|
+
broken_line << "\n#{line}"
|
73
|
+
next
|
74
|
+
end
|
75
|
+
broken_line = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# Figure out the indentation. Kinda ugly/slow way to support tabs,
|
79
|
+
# but remember that this is only done at parsing time.
|
80
|
+
indent = line[/^[ \t]*/].gsub("\t", @tab).size
|
81
|
+
|
82
|
+
# Remove the indentation
|
83
|
+
line.lstrip!
|
84
|
+
|
85
|
+
if line.strip.empty? || line[0] == ?/
|
86
|
+
# This happens to be an empty line or a comment, so we'll just have to make sure
|
87
|
+
# the generated code includes a newline (so the line numbers in the
|
88
|
+
# stack trace for an exception matches the ones in the template).
|
89
|
+
stacks.last << [:newline]
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
# Handle text blocks with multiple lines
|
94
|
+
if text_indent
|
95
|
+
if indent > text_indent
|
96
|
+
# This line happens to be indented deeper (or equal) than the block start character (|, ', `).
|
97
|
+
# This means that it's a part of the text block.
|
98
|
+
|
99
|
+
# The indentation of first line of the text block determines the text base indentation.
|
100
|
+
text_base_indent ||= indent
|
101
|
+
|
102
|
+
# The text block lines must be at least indented as deep as the first line.
|
103
|
+
offset = indent - text_base_indent
|
104
|
+
syntax_error! 'Unexpected text indentation', line, lineno if offset < 0
|
105
|
+
|
106
|
+
# Generate the additional spaces in front.
|
107
|
+
i = ' ' * offset
|
108
|
+
stacks.last << [:slim, :text, i + line]
|
109
|
+
stacks.last << [:newline]
|
110
|
+
|
111
|
+
# Mark this line as it's been indented as the text block start character.
|
112
|
+
indent = text_indent
|
113
|
+
|
114
|
+
next
|
115
|
+
end
|
116
|
+
|
117
|
+
# It's guaranteed that we're now *not* in a text block, because
|
118
|
+
# the indent will always be set to the text block start indent.
|
119
|
+
text_indent = text_base_indent = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# If there's more stacks than indents, it means that the previous
|
123
|
+
# line is expecting this line to be indented.
|
124
|
+
expecting_indentation = stacks.size > indents.size
|
125
|
+
|
126
|
+
if indent > indents.last
|
127
|
+
# This line was actually indented, so we'll have to check if it was
|
128
|
+
# supposed to be indented or not.
|
129
|
+
syntax_error! 'Unexpected indentation', line, lineno unless expecting_indentation
|
130
|
+
|
131
|
+
indents << indent
|
132
|
+
else
|
133
|
+
# This line was *not* indented more than the line before,
|
134
|
+
# so we'll just forget about the stack that the previous line pushed.
|
135
|
+
stacks.pop if expecting_indentation
|
136
|
+
|
137
|
+
# This line was deindented.
|
138
|
+
# Now we're have to go through the all the indents and figure out
|
139
|
+
# how many levels we've deindented.
|
140
|
+
while indent < indents.last
|
141
|
+
indents.pop
|
142
|
+
stacks.pop
|
143
|
+
end
|
144
|
+
|
145
|
+
# This line's indentation happens lie "between" two other line's
|
146
|
+
# indentation:
|
147
|
+
#
|
148
|
+
# hello
|
149
|
+
# world
|
150
|
+
# this # <- This should not be possible!
|
151
|
+
syntax_error! 'Malformed indentation', line, lineno if indents.last < indent
|
152
|
+
end
|
153
|
+
|
154
|
+
case line[0]
|
155
|
+
when ?|, ?', ?`
|
156
|
+
# Found a piece of text.
|
157
|
+
|
158
|
+
# We're now expecting the next line to be indented, so we'll need
|
159
|
+
# to push a block to the stack.
|
160
|
+
block = [:multi]
|
161
|
+
stacks.last << block
|
162
|
+
stacks << block
|
163
|
+
text_indent = indent
|
164
|
+
|
165
|
+
line.slice!(0)
|
166
|
+
if !line.strip.empty?
|
167
|
+
block << [:slim, :text, line.sub(/^( )/, '')]
|
168
|
+
text_base_indent = text_indent + ($1 ? 2 : 1)
|
169
|
+
end
|
170
|
+
when ?-, ?=
|
171
|
+
# Found a potential code block.
|
172
|
+
|
173
|
+
# First of all we need to push a exp into the stack. Anything
|
174
|
+
# indented deeper will be pushed into this exp. We'll include the
|
175
|
+
# same exp in the current-stack, which makes sure that it'll be
|
176
|
+
# included in the generated code.
|
177
|
+
block = [:multi]
|
178
|
+
if line[1] == ?=
|
179
|
+
broken_line = line[2..-1].strip
|
180
|
+
stacks.last << [:slim, :output, false, broken_line, block]
|
181
|
+
elsif line[0] == ?=
|
182
|
+
broken_line = line[1..-1].strip
|
183
|
+
stacks.last << [:slim, :output, true, broken_line, block]
|
184
|
+
else
|
185
|
+
broken_line = line[1..-1].strip
|
186
|
+
stacks.last << [:slim, :control, broken_line, block]
|
187
|
+
end
|
188
|
+
stacks << block
|
189
|
+
when ?!
|
190
|
+
# Found a directive (currently only used for doctypes)
|
191
|
+
stacks.last << [:slim, :directive, line[1..-1].strip]
|
192
|
+
else
|
193
|
+
if line =~ /^(\w+):\s*$/
|
194
|
+
# Embedded template detected. It is treated like a text block.
|
195
|
+
block = [:slim, :embedded, $1]
|
196
|
+
stacks.last << block
|
197
|
+
stacks << block
|
198
|
+
text_indent = indent
|
199
|
+
else
|
200
|
+
# Found a HTML tag.
|
201
|
+
exp, content, broken_line = parse_tag(line, lineno)
|
202
|
+
stacks.last << exp
|
203
|
+
stacks << content if content
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
result
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
ATTR_REGEX = /^ ([\w-]+)=/
|
214
|
+
QUOTED_VALUE_REGEX = /^("[^"]+"|'[^']+')/
|
215
|
+
ATTR_SHORTHAND = {
|
216
|
+
'#' => 'id',
|
217
|
+
'.' => 'class',
|
218
|
+
}
|
219
|
+
DELIMITERS = {
|
220
|
+
'(' => ')',
|
221
|
+
'[' => ']',
|
222
|
+
'{' => '}',
|
223
|
+
}
|
224
|
+
DELIMITER_REGEX = /^([\(\[\{])/
|
225
|
+
CLOSE_DELIMITER_REGEX = /^([\)\]\}])/
|
226
|
+
if RUBY_VERSION > '1.9'
|
227
|
+
CLASS_ID_REGEX = /^(#|\.)([\w\u00c0-\uFFFF][\w:\u00c0-\uFFFF-]*)/
|
228
|
+
else
|
229
|
+
CLASS_ID_REGEX = /^(#|\.)([\w][\w:-]*)/
|
230
|
+
end
|
231
|
+
|
232
|
+
def parse_tag(line, lineno)
|
233
|
+
orig_line = line
|
234
|
+
|
235
|
+
if line =~ /^(#|\.)/
|
236
|
+
tag = 'div'
|
237
|
+
elsif line =~ /^[\w:]+/
|
238
|
+
tag = $&
|
239
|
+
line = $'
|
240
|
+
else
|
241
|
+
syntax_error! 'Unknown line indicator', orig_line, lineno
|
242
|
+
end
|
243
|
+
|
244
|
+
# Now we'll have to find all the attributes. We'll store these in an
|
245
|
+
# nested array: [[name, value], [name2, value2]]. The value is a piece
|
246
|
+
# of Ruby code.
|
247
|
+
attributes = []
|
248
|
+
|
249
|
+
# Find any literal class/id attributes
|
250
|
+
while line =~ CLASS_ID_REGEX
|
251
|
+
attributes << [ATTR_SHORTHAND[$1], $2]
|
252
|
+
line = $'
|
253
|
+
end
|
254
|
+
|
255
|
+
# Check to see if there is a delimiter right after the tag name
|
256
|
+
delimiter = ''
|
257
|
+
if line =~ DELIMITER_REGEX
|
258
|
+
delimiter = DELIMITERS[$1]
|
259
|
+
# Replace the delimiter with a space so we can continue parsing as normal.
|
260
|
+
line[0] = ?\s
|
261
|
+
end
|
262
|
+
|
263
|
+
# Parse attributes
|
264
|
+
while line =~ ATTR_REGEX
|
265
|
+
key = $1
|
266
|
+
line = $'
|
267
|
+
if line =~ QUOTED_VALUE_REGEX
|
268
|
+
# Value is quote (static)
|
269
|
+
line = $'
|
270
|
+
value = $1[1..-2]
|
271
|
+
else
|
272
|
+
# Value is ruby code
|
273
|
+
line, value = parse_ruby_attribute(orig_line, line, lineno, delimiter)
|
274
|
+
end
|
275
|
+
attributes << [key, value]
|
276
|
+
end
|
277
|
+
|
278
|
+
# Find ending delimiter
|
279
|
+
if !delimiter.empty?
|
280
|
+
if line[0, 1] == delimiter
|
281
|
+
line.slice!(0)
|
282
|
+
else
|
283
|
+
syntax_error! "Expected closing attribute delimiter #{delimiter}", orig_line, lineno, orig_line.size - line.size
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
content = [:multi]
|
288
|
+
broken_line = nil
|
289
|
+
|
290
|
+
if line.strip.empty?
|
291
|
+
# If the line was empty there might be some indented content in the
|
292
|
+
# lines beneath it. We'll handle this by making this method return
|
293
|
+
# the block-variable. #compile will then push this onto the
|
294
|
+
# stacks-array.
|
295
|
+
block = content
|
296
|
+
elsif line =~ /^\s*=(=?)/
|
297
|
+
# Output
|
298
|
+
block = [:multi]
|
299
|
+
broken_line = $'.strip
|
300
|
+
content << [:slim, :output, $1 != '=', broken_line, block]
|
301
|
+
else
|
302
|
+
# Text content
|
303
|
+
line.sub!(/^ /, '')
|
304
|
+
content << [:slim, :text, line]
|
305
|
+
end
|
306
|
+
|
307
|
+
return [:slim, :tag, tag, attributes, content], block, broken_line
|
308
|
+
end
|
309
|
+
|
310
|
+
def parse_ruby_attribute(orig_line, line, lineno, delimiter)
|
311
|
+
# Delimiter stack
|
312
|
+
stack = []
|
313
|
+
|
314
|
+
# Attribute value buffer
|
315
|
+
value = ''
|
316
|
+
|
317
|
+
# Attribute ends with space or attribute delimiter
|
318
|
+
end_regex = /^[\s#{Regexp.escape delimiter}]/
|
319
|
+
|
320
|
+
until line.empty?
|
321
|
+
if stack.empty? && line =~ end_regex
|
322
|
+
# Stack is empty, this means we left the attribute value
|
323
|
+
# if next character is space or attribute delimiter
|
324
|
+
break
|
325
|
+
elsif line =~ DELIMITER_REGEX
|
326
|
+
# Delimiter found, push it on the stack
|
327
|
+
stack << DELIMITERS[$1]
|
328
|
+
value << line.slice!(0)
|
329
|
+
elsif line =~ CLOSE_DELIMITER_REGEX
|
330
|
+
# Closing delimiter found, pop it from the stack if everything is ok
|
331
|
+
syntax_error! "Unexpected closing #{$1}", orig_line, lineno if stack.empty?
|
332
|
+
syntax_error! "Expected closing #{stack.last}", orig_line, lineno if stack.last != $1
|
333
|
+
value << line.slice!(0)
|
334
|
+
stack.pop
|
335
|
+
else
|
336
|
+
value << line.slice!(0)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
syntax_error! "Expected closing attribute delimiter #{stack.last}", orig_line, lineno if !stack.empty?
|
341
|
+
syntax_error! 'Invalid empty attribute', orig_line, lineno if value.empty?
|
342
|
+
|
343
|
+
# Remove attribute wrapper which doesn't belong to the ruby code
|
344
|
+
# e.g id=[hash[:a] + hash[:b]]
|
345
|
+
value = value[1..-2] if value =~ DELIMITER_REGEX && DELIMITERS[value[0, 1]] == value[-1, 1]
|
346
|
+
|
347
|
+
[line, '#{%s}' % value]
|
348
|
+
end
|
349
|
+
|
350
|
+
# A little helper for raising exceptions.
|
351
|
+
def syntax_error!(*args)
|
352
|
+
raise SyntaxError.new(*args)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|