slim 0.7.0.beta.2 → 0.7.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.
- 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
|