slim 0.7.0.beta.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +6 -4
- data/README.md +23 -8
- data/Rakefile +22 -8
- data/benchmarks/run.rb +3 -3
- data/benchmarks/src/complex.slim +1 -1
- data/benchmarks/src/complex_view.rb +1 -1
- data/bin/slim +7 -0
- data/extra/slim-mode.el +409 -0
- data/{vim → extra}/slim.vim +0 -0
- data/extra/test.slim +41 -0
- data/lib/slim.rb +2 -1
- data/lib/slim/command.rb +80 -0
- data/lib/slim/compiler.rb +67 -28
- data/lib/slim/embedded_engine.rb +21 -30
- data/lib/slim/end_inserter.rb +3 -0
- data/lib/slim/engine.rb +4 -9
- data/lib/slim/filter.rb +4 -0
- data/lib/slim/helpers.rb +46 -2
- data/lib/slim/parser.rb +86 -73
- data/lib/slim/rails.rb +2 -1
- data/lib/slim/template.rb +21 -1
- data/lib/slim/version.rb +1 -1
- data/slim.gemspec +4 -3
- data/test/helper.rb +21 -3
- data/test/slim/test_code_evaluation.rb +2 -2
- data/test/slim/test_code_helpers.rb +18 -0
- data/test/slim/test_code_structure.rb +3 -2
- data/test/slim/test_embedded_engines.rb +11 -2
- data/test/slim/test_html_structure.rb +45 -0
- data/test/slim/test_parser_errors.rb +20 -11
- data/test/slim/test_ruby_errors.rb +116 -0
- metadata +31 -16
- data/vim/test.slim +0 -27
data/lib/slim/parser.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
module Slim
|
2
|
+
# Parses Slim code and transforms it to a Temple expression
|
3
|
+
# @api private
|
2
4
|
class Parser
|
3
5
|
class SyntaxError < StandardError
|
4
|
-
|
5
|
-
|
6
|
+
attr_reader :error, :file, :line, :lineno, :column
|
7
|
+
|
8
|
+
def initialize(error, file, line, lineno, column = 0)
|
9
|
+
@error = error
|
10
|
+
@file = file || '(__TEMPLATE__)'
|
6
11
|
@line = line.strip
|
7
12
|
@lineno = lineno
|
8
13
|
@column = column
|
9
14
|
end
|
10
15
|
|
11
16
|
def to_s
|
12
|
-
%{#{
|
13
|
-
Line #{
|
14
|
-
#{
|
15
|
-
#{' ' *
|
17
|
+
%{#{error}
|
18
|
+
#{file}, Line #{lineno}
|
19
|
+
#{line}
|
20
|
+
#{' ' * column}^
|
16
21
|
}
|
17
22
|
end
|
18
23
|
end
|
@@ -24,6 +29,10 @@ module Slim
|
|
24
29
|
@tab = ' ' * (options[:tabsize] || 4)
|
25
30
|
end
|
26
31
|
|
32
|
+
# Compile string to Temple expression
|
33
|
+
#
|
34
|
+
# @param [String] str Slim code
|
35
|
+
# @return [Array] Temple expression representing the code
|
27
36
|
def compile(str)
|
28
37
|
lineno = 0
|
29
38
|
result = [:multi]
|
@@ -58,7 +67,7 @@ module Slim
|
|
58
67
|
# Hello
|
59
68
|
# World!
|
60
69
|
#
|
61
|
-
|
70
|
+
block_indent, in_comment, text_indent = nil, false, nil
|
62
71
|
|
63
72
|
str.each_line do |line|
|
64
73
|
lineno += 1
|
@@ -69,12 +78,20 @@ module Slim
|
|
69
78
|
# Handle broken lines
|
70
79
|
if broken_line
|
71
80
|
if broken_line[-1] == ?\\
|
72
|
-
broken_line << "\n
|
81
|
+
broken_line << "\n" << line
|
73
82
|
next
|
74
83
|
end
|
75
84
|
broken_line = nil
|
76
85
|
end
|
77
86
|
|
87
|
+
if line.strip.empty?
|
88
|
+
# This happens to be an empty line, so we'll just have to make sure
|
89
|
+
# the generated code includes a newline (so the line numbers in the
|
90
|
+
# stack trace for an exception matches the ones in the template).
|
91
|
+
stacks.last << [:newline]
|
92
|
+
next
|
93
|
+
end
|
94
|
+
|
78
95
|
# Figure out the indentation. Kinda ugly/slow way to support tabs,
|
79
96
|
# but remember that this is only done at parsing time.
|
80
97
|
indent = line[/^[ \t]*/].gsub("\t", @tab).size
|
@@ -82,41 +99,33 @@ module Slim
|
|
82
99
|
# Remove the indentation
|
83
100
|
line.lstrip!
|
84
101
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
next
|
91
|
-
end
|
102
|
+
# Handle blocks with multiple lines
|
103
|
+
if block_indent
|
104
|
+
if indent > block_indent
|
105
|
+
# This line happens to be indented deeper (or equal) than the block start character (|, ', `, /).
|
106
|
+
# This means that it's a part of the block.
|
92
107
|
|
93
|
-
|
94
|
-
|
95
|
-
|
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.
|
108
|
+
if !in_comment
|
109
|
+
# The indentation of first line of the text block determines the text base indentation.
|
110
|
+
text_indent ||= indent
|
98
111
|
|
99
|
-
|
100
|
-
|
112
|
+
# The text block lines must be at least indented as deep as the first line.
|
113
|
+
offset = indent - text_indent
|
114
|
+
syntax_error! 'Unexpected text indentation', line, lineno if offset < 0
|
101
115
|
|
102
|
-
|
103
|
-
|
104
|
-
|
116
|
+
# Generate the additional spaces in front.
|
117
|
+
i = ' ' * offset
|
118
|
+
stacks.last << [:slim, :text, i + line]
|
119
|
+
end
|
105
120
|
|
106
|
-
# Generate the additional spaces in front.
|
107
|
-
i = ' ' * offset
|
108
|
-
stacks.last << [:slim, :text, i + line]
|
109
121
|
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
122
|
next
|
115
123
|
end
|
116
124
|
|
117
|
-
# It's guaranteed that we're now *not* in a
|
118
|
-
# the indent
|
119
|
-
|
125
|
+
# It's guaranteed that we're now *not* in a block, because
|
126
|
+
# the indent was less than the block start indent.
|
127
|
+
block_indent = text_indent = nil
|
128
|
+
in_comment = false
|
120
129
|
end
|
121
130
|
|
122
131
|
# If there's more stacks than indents, it means that the previous
|
@@ -152,20 +161,21 @@ module Slim
|
|
152
161
|
end
|
153
162
|
|
154
163
|
case line[0]
|
155
|
-
when ?|, ?',
|
156
|
-
# Found a
|
164
|
+
when ?|, ?', ?`, ?/
|
165
|
+
# Found a block.
|
157
166
|
|
158
167
|
# We're now expecting the next line to be indented, so we'll need
|
159
168
|
# to push a block to the stack.
|
160
169
|
block = [:multi]
|
161
170
|
stacks.last << block
|
162
171
|
stacks << block
|
163
|
-
|
172
|
+
block_indent = indent
|
164
173
|
|
174
|
+
in_comment = line[0] == ?/
|
165
175
|
line.slice!(0)
|
166
|
-
if !line.strip.empty?
|
176
|
+
if !in_comment && !line.strip.empty?
|
167
177
|
block << [:slim, :text, line.sub(/^( )/, '')]
|
168
|
-
|
178
|
+
text_indent = block_indent + ($1 ? 2 : 1)
|
169
179
|
end
|
170
180
|
when ?-, ?=
|
171
181
|
# Found a potential code block.
|
@@ -191,18 +201,24 @@ module Slim
|
|
191
201
|
stacks.last << [:slim, :directive, line[1..-1].strip]
|
192
202
|
else
|
193
203
|
if line =~ /^(\w+):\s*$/
|
194
|
-
# Embedded template detected. It is treated
|
204
|
+
# Embedded template detected. It is treated as block.
|
195
205
|
block = [:slim, :embedded, $1]
|
196
|
-
stacks.last << block
|
206
|
+
stacks.last << [:newline] << block
|
197
207
|
stacks << block
|
198
|
-
|
208
|
+
block_indent = indent
|
209
|
+
next
|
199
210
|
else
|
200
211
|
# Found a HTML tag.
|
201
|
-
|
202
|
-
stacks.last <<
|
203
|
-
stacks <<
|
212
|
+
tag, block, broken_line, text_indent = parse_tag(line, lineno)
|
213
|
+
stacks.last << tag
|
214
|
+
stacks << block if block
|
215
|
+
if text_indent
|
216
|
+
block_indent = indent
|
217
|
+
text_indent += indent
|
218
|
+
end
|
204
219
|
end
|
205
220
|
end
|
221
|
+
stacks.last << [:newline]
|
206
222
|
end
|
207
223
|
|
208
224
|
result
|
@@ -210,31 +226,32 @@ module Slim
|
|
210
226
|
|
211
227
|
private
|
212
228
|
|
213
|
-
ATTR_REGEX = /^ (
|
229
|
+
ATTR_REGEX = /^ (\w[:\w-]*)=/
|
214
230
|
QUOTED_VALUE_REGEX = /^("[^"]+"|'[^']+')/
|
215
231
|
ATTR_SHORTHAND = {
|
216
232
|
'#' => 'id',
|
217
233
|
'.' => 'class',
|
218
|
-
}
|
234
|
+
}.freeze
|
219
235
|
DELIMITERS = {
|
220
236
|
'(' => ')',
|
221
237
|
'[' => ']',
|
222
238
|
'{' => '}',
|
223
|
-
}
|
239
|
+
}.freeze
|
224
240
|
DELIMITER_REGEX = /^([\(\[\{])/
|
225
241
|
CLOSE_DELIMITER_REGEX = /^([\)\]\}])/
|
226
242
|
if RUBY_VERSION > '1.9'
|
227
243
|
CLASS_ID_REGEX = /^(#|\.)([\w\u00c0-\uFFFF][\w:\u00c0-\uFFFF-]*)/
|
228
244
|
else
|
229
|
-
CLASS_ID_REGEX = /^(#|\.)(
|
245
|
+
CLASS_ID_REGEX = /^(#|\.)(\w[\w:-]*)/
|
230
246
|
end
|
231
247
|
|
232
248
|
def parse_tag(line, lineno)
|
233
249
|
orig_line = line
|
234
250
|
|
235
|
-
|
251
|
+
case line
|
252
|
+
when /^(#|\.)/
|
236
253
|
tag = 'div'
|
237
|
-
|
254
|
+
when /^\w[:\w-]*/
|
238
255
|
tag = $&
|
239
256
|
line = $'
|
240
257
|
else
|
@@ -248,7 +265,7 @@ module Slim
|
|
248
265
|
|
249
266
|
# Find any literal class/id attributes
|
250
267
|
while line =~ CLASS_ID_REGEX
|
251
|
-
attributes << [ATTR_SHORTHAND[$1], $2]
|
268
|
+
attributes << [ATTR_SHORTHAND[$1], false, $2]
|
252
269
|
line = $'
|
253
270
|
end
|
254
271
|
|
@@ -265,14 +282,14 @@ module Slim
|
|
265
282
|
key = $1
|
266
283
|
line = $'
|
267
284
|
if line =~ QUOTED_VALUE_REGEX
|
268
|
-
# Value is
|
285
|
+
# Value is quoted (static)
|
269
286
|
line = $'
|
270
|
-
|
287
|
+
attributes << [key, false, $1[1..-2]]
|
271
288
|
else
|
272
289
|
# Value is ruby code
|
273
290
|
line, value = parse_ruby_attribute(orig_line, line, lineno, delimiter)
|
291
|
+
attributes << [key, true, value]
|
274
292
|
end
|
275
|
-
attributes << [key, value]
|
276
293
|
end
|
277
294
|
|
278
295
|
# Find ending delimiter
|
@@ -285,26 +302,22 @@ module Slim
|
|
285
302
|
end
|
286
303
|
|
287
304
|
content = [:multi]
|
288
|
-
|
305
|
+
tag = [:slim, :tag, tag, attributes, content]
|
289
306
|
|
290
|
-
if line
|
291
|
-
#
|
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
|
307
|
+
if line =~ /^\s*=(=?)/
|
308
|
+
# Handle output code
|
298
309
|
block = [:multi]
|
299
310
|
broken_line = $'.strip
|
300
311
|
content << [:slim, :output, $1 != '=', broken_line, block]
|
312
|
+
[tag, block, broken_line, nil]
|
313
|
+
elsif !line.empty?
|
314
|
+
# Handle text content
|
315
|
+
content << [:slim, :text, line.sub(/^( )/, '')]
|
316
|
+
[tag, content, nil, orig_line.size - line.size + ($1 ? 1 : 0)]
|
301
317
|
else
|
302
|
-
#
|
303
|
-
|
304
|
-
content << [:slim, :text, line]
|
318
|
+
# Empty line
|
319
|
+
[tag, content, nil, nil]
|
305
320
|
end
|
306
|
-
|
307
|
-
return [:slim, :tag, tag, attributes, content], block, broken_line
|
308
321
|
end
|
309
322
|
|
310
323
|
def parse_ruby_attribute(orig_line, line, lineno, delimiter)
|
@@ -344,12 +357,12 @@ module Slim
|
|
344
357
|
# e.g id=[hash[:a] + hash[:b]]
|
345
358
|
value = value[1..-2] if value =~ DELIMITER_REGEX && DELIMITERS[value[0, 1]] == value[-1, 1]
|
346
359
|
|
347
|
-
|
360
|
+
return line, value
|
348
361
|
end
|
349
362
|
|
350
363
|
# A little helper for raising exceptions.
|
351
|
-
def syntax_error!(*args)
|
352
|
-
raise SyntaxError.new(*args)
|
364
|
+
def syntax_error!(message, *args)
|
365
|
+
raise SyntaxError.new(message, @options[:file], *args)
|
353
366
|
end
|
354
367
|
end
|
355
368
|
end
|
data/lib/slim/rails.rb
CHANGED
@@ -2,11 +2,12 @@ require 'slim'
|
|
2
2
|
|
3
3
|
module ActionView
|
4
4
|
module TemplateHandlers
|
5
|
+
# Slim handler for Rails 3
|
5
6
|
class SlimHandler < TemplateHandler
|
6
7
|
include Compilable
|
7
8
|
|
8
9
|
def compile(template)
|
9
|
-
|
10
|
+
Slim::Engine.new(:use_html_safe => true).compile(template.source)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
data/lib/slim/template.rb
CHANGED
@@ -1,14 +1,34 @@
|
|
1
1
|
module Slim
|
2
|
+
# Tilt template implementation for Slim
|
3
|
+
# @api public
|
2
4
|
class Template < Tilt::Template
|
5
|
+
# Prepare Slim template
|
6
|
+
#
|
7
|
+
# Called immediately after template data is loaded.
|
8
|
+
#
|
9
|
+
# @return [void]
|
3
10
|
def prepare
|
4
|
-
@src = Engine.new(options).compile(data)
|
11
|
+
@src = Engine.new(options.merge(:file => eval_file)).compile(data)
|
5
12
|
end
|
6
13
|
|
14
|
+
# Process the template and return the result.
|
15
|
+
#
|
16
|
+
# Template executationis guaranteed to be performed in the scope object with the locals
|
17
|
+
# specified and with support for yielding to the block.
|
18
|
+
#
|
19
|
+
# @param [Object] scope Scope object where the code is evaluated
|
20
|
+
# @param [Hash] locals Local variables
|
21
|
+
# @yield Block given to the template code
|
22
|
+
# @return [String] Evaluated template
|
7
23
|
def evaluate(scope, locals, &block)
|
8
24
|
scope.instance_eval { extend Slim::Helpers } if options[:helpers]
|
9
25
|
super
|
10
26
|
end
|
11
27
|
|
28
|
+
# A string containing the (Ruby) source code for the template.
|
29
|
+
#
|
30
|
+
# @param [Hash] locals Local variables
|
31
|
+
# @return [String] Compiled template ruby code
|
12
32
|
def precompiled_template(locals)
|
13
33
|
@src
|
14
34
|
end
|
data/lib/slim/version.rb
CHANGED
data/slim.gemspec
CHANGED
@@ -5,8 +5,8 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.name = "slim"
|
6
6
|
s.version = Slim::VERSION
|
7
7
|
s.date = Date.today.to_s
|
8
|
-
s.authors = ["Andrew Stone", "Fred Wu"]
|
9
|
-
s.email = ["andy@stonean.com", "ifredwu@gmail.com"]
|
8
|
+
s.authors = ["Andrew Stone", "Fred Wu", "Daniel Mendler"]
|
9
|
+
s.email = ["andy@stonean.com", "ifredwu@gmail.com", "mail@daniel-mendler.de"]
|
10
10
|
s.summary = %q{Slim is a template language.}
|
11
11
|
s.description = %q{Slim is a template language whose goal is reduce the syntax to the essential parts without becoming cryptic.}
|
12
12
|
s.homepage = %q{http://github.com/stonean/slim}
|
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
23
|
s.add_runtime_dependency(%q<escape_utils>, [">= 0.1.9"]) unless RUBY_PLATFORM == "java"
|
24
|
-
s.add_runtime_dependency(%q<temple>, ["~> 0.1.
|
24
|
+
s.add_runtime_dependency(%q<temple>, ["~> 0.1.3"])
|
25
25
|
s.add_runtime_dependency(%q<tilt>, ["~> 1.1"])
|
26
26
|
s.add_development_dependency(%q<rake>, [">= 0.8.7"])
|
27
27
|
s.add_development_dependency(%q<haml>, [">= 0"])
|
@@ -30,4 +30,5 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
31
31
|
s.add_development_dependency(%q<rdiscount>, [">= 0"])
|
32
32
|
s.add_development_dependency(%q<liquid>, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
33
34
|
end
|
data/test/helper.rb
CHANGED
@@ -14,18 +14,36 @@ class TestSlim < MiniTest::Unit::TestCase
|
|
14
14
|
|
15
15
|
def teardown
|
16
16
|
String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?)
|
17
|
+
String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe)
|
18
|
+
Object.send(:undef_method, :html_safe?) if Object.method_defined?(:html_safe?)
|
17
19
|
Slim::Filter::DEFAULT_OPTIONS.delete(:use_html_safe)
|
18
20
|
end
|
19
21
|
|
20
22
|
def assert_html(expected, source, options = {})
|
21
|
-
assert_equal expected, Slim::
|
23
|
+
assert_equal expected, Slim::Template.new(options[:file], options) { source }.render(@env)
|
22
24
|
end
|
23
25
|
|
24
26
|
def assert_syntax_error(message, source, options = {})
|
25
|
-
Slim::
|
27
|
+
Slim::Template.new(options[:file], options) { source }.render(@env)
|
26
28
|
raise 'Syntax error expected'
|
27
29
|
rescue Slim::Parser::SyntaxError => ex
|
28
|
-
assert_equal
|
30
|
+
assert_equal message, ex.message
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_ruby_error(error, from, source, options = {})
|
34
|
+
Slim::Template.new(options[:file], options) { source }.render(@env)
|
35
|
+
raise 'Ruby error expected'
|
36
|
+
rescue error => ex
|
37
|
+
ex.backtrace[0] =~ /^(.*?:\d+):/
|
38
|
+
assert_equal from, $1
|
39
|
+
end
|
40
|
+
|
41
|
+
def assert_ruby_syntax_error(from, source, options = {})
|
42
|
+
Slim::Template.new(options[:file], options) { source }.render(@env)
|
43
|
+
raise 'Ruby syntax error expected'
|
44
|
+
rescue SyntaxError => ex
|
45
|
+
ex.message =~ /^(.*?:\d+):/
|
46
|
+
assert_equal from, $1
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
@@ -157,13 +157,13 @@ p id=(1 + 1)*5 Test it
|
|
157
157
|
def test_interpolation_in_text
|
158
158
|
source = %q{
|
159
159
|
p
|
160
|
-
| #{hello_world}
|
160
|
+
| #{hello_world} with "quotes"
|
161
161
|
p
|
162
162
|
|
|
163
163
|
A message from the compiler: #{hello_world}
|
164
164
|
}
|
165
165
|
|
166
|
-
assert_html '<p>Hello World from @env</p><p>A message from the compiler: Hello World from @env</p>', source
|
166
|
+
assert_html '<p>Hello World from @env with "quotes"</p><p>A message from the compiler: Hello World from @env</p>', source
|
167
167
|
end
|
168
168
|
|
169
169
|
def test_interpolation_in_tag
|
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestSlimHelpers < TestSlim
|
4
|
+
class HtmlSafeString < String
|
5
|
+
def html_safe?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
def test_list_of
|
5
11
|
source = %q{
|
6
12
|
== list_of([1, 2, 3]) do |i|
|
@@ -9,4 +15,16 @@ class TestSlimHelpers < TestSlim
|
|
9
15
|
|
10
16
|
assert_html "<li>1</li>\n<li>2</li>\n<li>3</li>", source, :helpers => true
|
11
17
|
end
|
18
|
+
|
19
|
+
def test_list_of_with_html_safe
|
20
|
+
Object.send(:define_method, :html_safe?) { false }
|
21
|
+
String.send(:define_method, :html_safe) { HtmlSafeString.new(self) }
|
22
|
+
|
23
|
+
source = %q{
|
24
|
+
= list_of([1, 2, 3]) do |i|
|
25
|
+
= i
|
26
|
+
}
|
27
|
+
|
28
|
+
html = Slim::Template.new(:helpers => true, :use_html_safe => true) { source }.render(@env)
|
29
|
+
end
|
12
30
|
end
|