slim 0.6.1 → 0.7.0.beta.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|