slim 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.yardopts +2 -0
- data/CHANGES +147 -0
- data/Gemfile +16 -0
- data/LICENSE +21 -0
- data/README.md +31 -36
- data/benchmarks/run.rb +114 -0
- data/benchmarks/src/complex.erb +23 -0
- data/benchmarks/src/complex.haml +18 -0
- data/benchmarks/src/complex.slim +17 -0
- data/benchmarks/src/complex_view.rb +15 -0
- data/extra/slim-mode.el +409 -0
- data/extra/test.slim +49 -0
- data/lib/slim/command.rb +0 -6
- data/lib/slim/compiler.rb +40 -51
- data/lib/slim/embedded_engine.rb +69 -41
- data/lib/slim/end_inserter.rb +5 -3
- data/lib/slim/engine.rb +9 -11
- data/lib/slim/filter.rb +10 -13
- data/lib/slim/grammar.rb +19 -0
- data/lib/slim/interpolation.rb +7 -5
- data/lib/slim/parser.rb +48 -33
- data/lib/slim/sections.rb +22 -26
- data/lib/slim/template.rb +7 -7
- data/lib/slim/version.rb +3 -1
- data/lib/slim/wrapper.rb +1 -0
- data/slim.gemspec +34 -0
- data/test/helper.rb +5 -1
- data/test/slim/test_code_escaping.rb +3 -3
- data/test/slim/test_code_evaluation.rb +49 -2
- data/test/slim/test_embedded_engines.rb +3 -1
- data/test/slim/test_html_escaping.rb +8 -0
- data/test/slim/test_html_structure.rb +18 -0
- data/test/slim/test_pretty.rb +8 -3
- data/test/slim/test_ruby_errors.rb +27 -2
- metadata +38 -46
data/lib/slim/filter.rb
CHANGED
@@ -6,28 +6,25 @@ module Slim
|
|
6
6
|
# of the expression.
|
7
7
|
#
|
8
8
|
# @api private
|
9
|
-
class Filter < Temple::Filter
|
10
|
-
#
|
11
|
-
|
9
|
+
class Filter < Temple::HTML::Filter
|
10
|
+
# Pass-through handler
|
11
|
+
def on_slim_embedded(type, content)
|
12
|
+
[:slim, :embedded, code, compile(content)]
|
13
|
+
end
|
12
14
|
|
15
|
+
# Pass-through handler
|
13
16
|
def on_slim_control(code, content)
|
14
17
|
[:slim, :control, code, compile(content)]
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
20
|
+
# Pass-through handler
|
21
|
+
def on_slim_condcomment(condition, content)
|
22
|
+
[:slim, :condcomment, condition, compile(content)]
|
19
23
|
end
|
20
24
|
|
25
|
+
# Pass-through handler
|
21
26
|
def on_slim_output(code, escape, content)
|
22
27
|
[:slim, :output, code, escape, compile(content)]
|
23
28
|
end
|
24
|
-
|
25
|
-
def on_slim_tag(name, attrs, closed, content)
|
26
|
-
[:slim, :tag, name, compile(attrs), closed, compile(content)]
|
27
|
-
end
|
28
|
-
|
29
|
-
def on_slim_attrs(*attrs)
|
30
|
-
[:slim, :attrs, *attrs.map {|k, v| [k, compile(v)] }]
|
31
|
-
end
|
32
29
|
end
|
33
30
|
end
|
data/lib/slim/grammar.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Slim
|
2
|
+
# Slim expression grammar
|
3
|
+
# @api private
|
4
|
+
module Grammar
|
5
|
+
extend Temple::Grammar
|
6
|
+
|
7
|
+
Expression <<
|
8
|
+
[:slim, :control, String, Expression] |
|
9
|
+
[:slim, :condcomment, String, Expression] |
|
10
|
+
[:slim, :output, Bool, String, Expression] |
|
11
|
+
[:slim, :interpolate, String] |
|
12
|
+
[:slim, :embedded, String, Expression] |
|
13
|
+
[:slim, :directive, Value('doctype'), String]
|
14
|
+
|
15
|
+
HTMLAttr <<
|
16
|
+
[:slim, :attr, String, Bool, String]
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/slim/interpolation.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module Slim
|
2
2
|
# Perform interpolation of #{var_name} in the
|
3
|
-
# expressions `[:slim, :
|
3
|
+
# expressions `[:slim, :interpolate, string]`.
|
4
4
|
#
|
5
5
|
# @api private
|
6
6
|
class Interpolation < Filter
|
7
|
-
# Handle
|
7
|
+
# Handle interpolate expression `[:slim, :interpolate, string]`
|
8
8
|
#
|
9
|
-
# @param [String] string Static
|
9
|
+
# @param [String] string Static interpolate
|
10
10
|
# @return [Array] Compiled temple expression
|
11
|
-
def
|
11
|
+
def on_slim_interpolate(string)
|
12
12
|
# Interpolate variables in text (#{variable}).
|
13
13
|
# Split the text into multiple dynamic and static parts.
|
14
14
|
block = [:multi]
|
@@ -22,7 +22,7 @@ module Slim
|
|
22
22
|
# Interpolation
|
23
23
|
string, code = parse_expression($')
|
24
24
|
escape = code !~ /^\{.*\}$/
|
25
|
-
block << [:
|
25
|
+
block << [:escape, escape, [:dynamic, escape ? code : code[1..-2]]]
|
26
26
|
when /^([^#]+|#)/
|
27
27
|
# Static text
|
28
28
|
block << [:static, $&]
|
@@ -32,6 +32,8 @@ module Slim
|
|
32
32
|
block
|
33
33
|
end
|
34
34
|
|
35
|
+
protected
|
36
|
+
|
35
37
|
def parse_expression(string)
|
36
38
|
stack, code = [], ''
|
37
39
|
|
data/lib/slim/parser.rb
CHANGED
@@ -37,7 +37,14 @@ module Slim
|
|
37
37
|
# @param [String] str Slim code
|
38
38
|
# @return [Array] Temple expression representing the code
|
39
39
|
def call(str)
|
40
|
-
|
40
|
+
# Set string encoding if option is set
|
41
|
+
if options[:encoding] && str.respond_to?(:encoding)
|
42
|
+
old = str.encoding
|
43
|
+
str = str.dup if str.frozen?
|
44
|
+
str.force_encoding(options[:encoding])
|
45
|
+
# Fall back to old encoding if new encoding is invalid
|
46
|
+
str.force_encoding(old_enc) unless str.valid_encoding?
|
47
|
+
end
|
41
48
|
|
42
49
|
lineno = 0
|
43
50
|
result = [:multi]
|
@@ -110,7 +117,7 @@ module Slim
|
|
110
117
|
# This line happens to be indented deeper (or equal) than the block start character (|, ', /).
|
111
118
|
# This means that it's a part of the block.
|
112
119
|
|
113
|
-
|
120
|
+
unless in_comment
|
114
121
|
# The indentation of first line of the text block determines the text base indentation.
|
115
122
|
newline = text_indent ? "\n" : ''
|
116
123
|
text_indent ||= indent
|
@@ -120,7 +127,7 @@ module Slim
|
|
120
127
|
syntax_error! 'Unexpected text indentation', line, lineno if offset < 0
|
121
128
|
|
122
129
|
# Generate the additional spaces in front.
|
123
|
-
stacks.last << [:slim, :
|
130
|
+
stacks.last << [:slim, :interpolate, newline + (' ' * offset) + line]
|
124
131
|
end
|
125
132
|
|
126
133
|
stacks.last << [:newline]
|
@@ -166,29 +173,36 @@ module Slim
|
|
166
173
|
end
|
167
174
|
|
168
175
|
case line[0]
|
169
|
-
when
|
170
|
-
# Found a block.
|
171
|
-
ch = line.slice!(0)
|
172
|
-
|
173
|
-
# We're now expecting the next line to be indented, so we'll need
|
174
|
-
# to push a block to the stack.
|
176
|
+
when ?/
|
177
|
+
# Found a comment block.
|
175
178
|
block = [:multi]
|
176
|
-
stacks.last << if
|
177
|
-
# Additional whitespace in front
|
178
|
-
[:multi, block, [:slim, :text, ' ']]
|
179
|
-
elsif ch == ?/ && line[0] == ?!
|
179
|
+
stacks.last << if line =~ %r{^/!( ?)(.*)$}
|
180
180
|
# HTML comment
|
181
|
-
|
182
|
-
|
181
|
+
block_indent = indent
|
182
|
+
text_indent = block_indent + ($1 ? 2 : 1)
|
183
|
+
block << [:slim, :interpolate, $2] if $2
|
184
|
+
[:html, :comment, block]
|
185
|
+
elsif line =~ %r{^/\[\s*(.*?)\s*\]\s*$}
|
186
|
+
# HTML conditional comment
|
187
|
+
[:slim, :condcomment, $1, block]
|
183
188
|
else
|
184
|
-
|
189
|
+
# Slim comment
|
190
|
+
block_indent = indent
|
191
|
+
in_comment = true
|
185
192
|
block
|
186
193
|
end
|
187
194
|
stacks << block
|
195
|
+
when ?|, ?'
|
196
|
+
# Found a text block.
|
197
|
+
# We're now expecting the next line to be indented, so we'll need
|
198
|
+
# to push a block to the stack.
|
199
|
+
block = [:multi]
|
188
200
|
block_indent = indent
|
189
|
-
|
190
|
-
|
191
|
-
|
201
|
+
stacks.last << (line.slice!(0) == ?' ?
|
202
|
+
[:multi, block, [:static, ' ']] : block)
|
203
|
+
stacks << block
|
204
|
+
unless line.strip.empty?
|
205
|
+
block << [:slim, :interpolate, line.sub(/^( )/, '')]
|
192
206
|
text_indent = block_indent + ($1 ? 2 : 1)
|
193
207
|
end
|
194
208
|
when ?-
|
@@ -213,8 +227,8 @@ module Slim
|
|
213
227
|
else
|
214
228
|
if line =~ /^(\w+):\s*$/
|
215
229
|
# Embedded template detected. It is treated as block.
|
216
|
-
block = [:
|
217
|
-
stacks.last << [:newline] << block
|
230
|
+
block = [:multi]
|
231
|
+
stacks.last << [:newline] << [:slim, :embedded, $1, block]
|
218
232
|
stacks << block
|
219
233
|
block_indent = indent
|
220
234
|
next
|
@@ -276,13 +290,13 @@ module Slim
|
|
276
290
|
# Now we'll have to find all the attributes. We'll store these in an
|
277
291
|
# nested array: [[name, value], [name2, value2]]. The value is a piece
|
278
292
|
# of Ruby code.
|
279
|
-
attributes = [:
|
293
|
+
attributes = [:html, :attrs]
|
280
294
|
|
281
295
|
# Find any literal class/id attributes
|
282
296
|
while line =~ CLASS_ID_REGEX
|
283
297
|
# The class/id attribute is :static instead of :slim :text,
|
284
298
|
# because we don't want text interpolation in .class or #id shortcut
|
285
|
-
attributes << [ATTR_SHORTHAND[$1], [:static, $2]]
|
299
|
+
attributes << [:html, :attr, ATTR_SHORTHAND[$1], [:static, $2]]
|
286
300
|
line = $'
|
287
301
|
end
|
288
302
|
|
@@ -296,21 +310,22 @@ module Slim
|
|
296
310
|
|
297
311
|
# Parse attributes
|
298
312
|
while line =~ ATTR_REGEX
|
299
|
-
|
313
|
+
name = $1
|
300
314
|
line = $'
|
301
315
|
if line =~ QUOTED_VALUE_REGEX
|
302
316
|
# Value is quoted (static)
|
303
317
|
line = $'
|
304
|
-
attributes << [
|
318
|
+
attributes << [:html, :attr, name, [:slim, :interpolate, $1[1..-2]]]
|
305
319
|
else
|
306
320
|
# Value is ruby code
|
307
|
-
|
308
|
-
|
321
|
+
escape = line[0] != ?=
|
322
|
+
line, code = parse_ruby_attribute(orig_line, escape ? line : line[1..-1], lineno, delimiter)
|
323
|
+
attributes << [:slim, :attr, name, escape, code]
|
309
324
|
end
|
310
325
|
end
|
311
326
|
|
312
327
|
# Find ending delimiter
|
313
|
-
|
328
|
+
unless delimiter.empty?
|
314
329
|
if line =~ /^\s*#{Regexp.escape delimiter}/
|
315
330
|
line = $'
|
316
331
|
else
|
@@ -319,7 +334,7 @@ module Slim
|
|
319
334
|
end
|
320
335
|
|
321
336
|
content = [:multi]
|
322
|
-
tag = [:
|
337
|
+
tag = [:html, :tag, tag, attributes, content]
|
323
338
|
|
324
339
|
if line =~ /^\s*=(=?)/
|
325
340
|
# Handle output code
|
@@ -329,14 +344,14 @@ module Slim
|
|
329
344
|
[tag, block, broken_line, nil]
|
330
345
|
elsif line =~ /^\s*\//
|
331
346
|
# Closed tag
|
332
|
-
tag
|
347
|
+
tag.pop
|
333
348
|
[tag, block, nil, nil]
|
334
349
|
elsif line =~ /^\s*$/
|
335
350
|
# Empty line
|
336
351
|
[tag, content, nil, nil]
|
337
352
|
else
|
338
353
|
# Handle text content
|
339
|
-
content << [:slim, :
|
354
|
+
content << [:slim, :interpolate, line.sub(/^( )/, '')]
|
340
355
|
[tag, content, nil, orig_line.size - line.size + ($1 ? 1 : 0)]
|
341
356
|
end
|
342
357
|
end
|
@@ -371,7 +386,7 @@ module Slim
|
|
371
386
|
end
|
372
387
|
end
|
373
388
|
|
374
|
-
syntax_error! "Expected closing attribute delimiter #{stack.last}", orig_line, lineno
|
389
|
+
syntax_error! "Expected closing attribute delimiter #{stack.last}", orig_line, lineno unless stack.empty?
|
375
390
|
syntax_error! 'Invalid empty attribute', orig_line, lineno if value.empty?
|
376
391
|
|
377
392
|
# Remove attribute wrapper which doesn't belong to the ruby code
|
@@ -381,7 +396,7 @@ module Slim
|
|
381
396
|
return line, value
|
382
397
|
end
|
383
398
|
|
384
|
-
#
|
399
|
+
# Helper for raising exceptions
|
385
400
|
def syntax_error!(message, *args)
|
386
401
|
raise SyntaxError.new(message, options[:file], *args)
|
387
402
|
end
|
data/lib/slim/sections.rb
CHANGED
@@ -20,7 +20,7 @@ module Slim
|
|
20
20
|
dictionary = options[:dictionary]
|
21
21
|
dictionary = "Slim::Wrapper.new(#{dictionary})" if options[:dictionary_access] == :wrapped
|
22
22
|
[:multi,
|
23
|
-
[:
|
23
|
+
[:code, "_slimdict = #{dictionary}"],
|
24
24
|
super]
|
25
25
|
else
|
26
26
|
exp
|
@@ -36,38 +36,34 @@ module Slim
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def on_slim_output(escape, name, content)
|
40
|
+
raise 'Output statements with content are forbidden in sections mode' if !empty_exp?(content)
|
41
|
+
[:slim, :output, escape, access(name), content]
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
39
46
|
def on_slim_inverted_section(name, content)
|
40
|
-
tmp =
|
47
|
+
tmp = unique_name
|
41
48
|
[:multi,
|
42
|
-
[:
|
43
|
-
[:
|
44
|
-
|
45
|
-
[:block, 'end']]
|
49
|
+
[:code, "#{tmp} = #{access name}"],
|
50
|
+
[:if, "!#{tmp} || #{tmp}.respond_to?(:empty) && #{tmp}.empty?",
|
51
|
+
compile(content)]]
|
46
52
|
end
|
47
53
|
|
48
54
|
def on_slim_section(name, content)
|
49
55
|
content = compile(content)
|
50
|
-
tmp1, tmp2 =
|
56
|
+
tmp1, tmp2 = unique_name, unique_name
|
51
57
|
|
52
|
-
[:
|
53
|
-
[:
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
content,
|
62
|
-
[:block, 'end'],
|
63
|
-
[:block, "_slimdict = #{tmp2}"],
|
64
|
-
[:block, 'end'],
|
65
|
-
[:block, 'end']]
|
66
|
-
end
|
67
|
-
|
68
|
-
def on_slim_output(escape, name, content)
|
69
|
-
raise 'Output statements with content are forbidden in sections mode' if !empty_exp?(content)
|
70
|
-
[:slim, :output, escape, access(name), content]
|
58
|
+
[:if, "#{tmp1} = #{access name}",
|
59
|
+
[:if, "#{tmp1} == true",
|
60
|
+
content,
|
61
|
+
[:multi,
|
62
|
+
# Wrap map in array because maps implement each
|
63
|
+
[:code, "#{tmp1} = [#{tmp1}] if #{tmp1}.respond_to?(:has_key?) || !#{tmp1}.respond_to?(:map)"],
|
64
|
+
[:code, "#{tmp2} = _slimdict"],
|
65
|
+
[:block, "#{tmp1}.each do |_slimdict|", content],
|
66
|
+
[:code, "_slimdict = #{tmp2}"]]]]
|
71
67
|
end
|
72
68
|
|
73
69
|
private
|
data/lib/slim/template.rb
CHANGED
@@ -6,12 +6,12 @@ module Slim
|
|
6
6
|
if Object.const_defined?(:Rails)
|
7
7
|
# Rails template implementation for Slim
|
8
8
|
# @api public
|
9
|
-
RailsTemplate = Temple::Templates::Rails(Slim::Engine,
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
RailsTemplate = Temple::Templates::Rails(Slim::Engine,
|
10
|
+
:register_as => :slim,
|
11
|
+
# Use rails-specific generator. This is necessary
|
12
|
+
# to support block capturing. Disable the internal slim capturing.
|
13
|
+
# Rails takes care of the capturing by itself.
|
14
|
+
:generator => Temple::Generators::RailsOutputBuffer,
|
15
|
+
:disable_capture => true)
|
16
16
|
end
|
17
17
|
end
|
data/lib/slim/version.rb
CHANGED
data/lib/slim/wrapper.rb
CHANGED
data/slim.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/lib/slim/version'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'slim'
|
7
|
+
s.version = Slim::VERSION
|
8
|
+
s.date = Date.today.to_s
|
9
|
+
s.authors = ['Andrew Stone', 'Fred Wu', 'Daniel Mendler']
|
10
|
+
s.email = ['andy@stonean.com', 'ifredwu@gmail.com', 'mail@daniel-mendler.de']
|
11
|
+
s.summary = 'Slim is a template language.'
|
12
|
+
s.description = 'Slim is a template language whose goal is reduce the syntax to the essential parts without becoming cryptic.'
|
13
|
+
s.homepage = 'http://github.com/stonean/slim'
|
14
|
+
s.extra_rdoc_files = %w(README.md)
|
15
|
+
s.rdoc_options = %w(--charset=UTF-8)
|
16
|
+
s.rubyforge_project = s.name
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = %w(lib)
|
21
|
+
|
22
|
+
s.add_runtime_dependency('temple', ['~> 0.3.0']) unless ENV['SLIM_USE_TEMPLE']
|
23
|
+
s.add_runtime_dependency('tilt', ['~> 1.2'])
|
24
|
+
|
25
|
+
s.add_development_dependency('rake', ['>= 0.8.7'])
|
26
|
+
s.add_development_dependency('haml', ['>= 3.1.0'])
|
27
|
+
s.add_development_dependency('sass', ['>= 3.1.0'])
|
28
|
+
s.add_development_dependency('minitest', ['>= 0'])
|
29
|
+
s.add_development_dependency('rcov', ['>= 0'])
|
30
|
+
s.add_development_dependency('rdiscount', ['>= 0'])
|
31
|
+
s.add_development_dependency('liquid', ['>= 0'])
|
32
|
+
s.add_development_dependency('yard', ['>= 0'])
|
33
|
+
s.add_development_dependency('creole', ['>= 0'])
|
34
|
+
end
|
data/test/helper.rb
CHANGED
@@ -3,13 +3,17 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'minitest/unit'
|
5
5
|
require 'slim'
|
6
|
+
require 'slim/grammar'
|
6
7
|
|
7
8
|
MiniTest::Unit.autorun
|
8
9
|
|
10
|
+
Slim::Engine.after Slim::Parser, Temple::Filters::Validator, :grammar => Slim::Grammar
|
11
|
+
Slim::Engine.before Slim::Compiler, Temple::Filters::Validator, :grammar => Slim::Grammar
|
12
|
+
Slim::Engine.before Temple::HTML::Pretty, Temple::Filters::Validator
|
13
|
+
|
9
14
|
class TestSlim < MiniTest::Unit::TestCase
|
10
15
|
def setup
|
11
16
|
@env = Env.new
|
12
|
-
# Slim::Engine.set_default_options :debug => true
|
13
17
|
end
|
14
18
|
|
15
19
|
def teardown
|
@@ -33,7 +33,7 @@ p = HtmlSafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
|
|
33
33
|
assert_html "<p><strong>Hello World\n, meet \"Slim\"</strong>.</p>", source, :use_html_safe => true
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def test_render_with_disable_escape_false
|
37
37
|
source = %q{
|
38
38
|
= "<p>Hello</p>"
|
39
39
|
== "<p>World</p>"
|
@@ -42,12 +42,12 @@ p = HtmlSafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
|
|
42
42
|
assert_html "<p>Hello</p><p>World</p>", source
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def test_render_with_disable_escape_true
|
46
46
|
source = %q{
|
47
47
|
= "<p>Hello</p>"
|
48
48
|
== "<p>World</p>"
|
49
49
|
}
|
50
50
|
|
51
|
-
assert_html "<p>Hello</p><p>World</p>", source, :
|
51
|
+
assert_html "<p>Hello</p><p>World</p>", source, :disable_escape => true
|
52
52
|
end
|
53
53
|
end
|