slim 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -6
- data/README.md +32 -56
- data/Rakefile +2 -2
- data/benchmarks/run.rb +49 -16
- data/lib/slim.rb +0 -8
- data/lib/slim/compiler.rb +17 -26
- data/lib/slim/embedded_engine.rb +18 -11
- data/lib/slim/end_inserter.rb +10 -11
- data/lib/slim/engine.rb +8 -3
- data/lib/slim/filter.rb +8 -37
- data/lib/slim/parser.rb +27 -25
- data/lib/slim/template.rb +2 -31
- data/lib/slim/version.rb +1 -1
- data/slim.gemspec +1 -2
- data/test/helper.rb +35 -8
- data/test/slim/test_code_blocks.rb +37 -2
- data/test/slim/test_code_escaping.rb +2 -2
- data/test/slim/test_code_evaluation.rb +31 -9
- data/test/slim/test_code_output.rb +9 -1
- data/test/slim/test_code_structure.rb +23 -13
- data/test/slim/test_html_structure.rb +32 -8
- data/test/slim/test_parser_errors.rb +2 -2
- data/test/slim/test_ruby_errors.rb +28 -0
- metadata +25 -42
- data/lib/slim/helpers.rb +0 -81
- data/test/slim/test_code_helpers.rb +0 -30
data/lib/slim/end_inserter.rb
CHANGED
@@ -10,7 +10,7 @@ module Slim
|
|
10
10
|
#
|
11
11
|
# @api private
|
12
12
|
class EndInserter < Filter
|
13
|
-
ELSE_REGEX = /^
|
13
|
+
ELSE_REGEX = /^else|elsif|when\b/
|
14
14
|
END_REGEX = /^end\b/
|
15
15
|
|
16
16
|
# Handle multi expression `[:multi, *exps]`
|
@@ -22,15 +22,14 @@ module Slim
|
|
22
22
|
|
23
23
|
exps.each do |exp|
|
24
24
|
if control?(exp)
|
25
|
-
if
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
25
|
+
raise 'Explicit end statements are forbidden' if exp[2] =~ END_REGEX
|
26
|
+
|
27
|
+
# Two control code in a row. If this one is *not*
|
28
|
+
# an else block, we should close the previous one.
|
29
|
+
append_end(result) if prev_indent && exp[2] !~ ELSE_REGEX
|
30
|
+
|
31
|
+
# Indent if the control code contains something.
|
32
|
+
prev_indent = !empty_exp?(exp[3])
|
34
33
|
elsif exp[0] != :newline && prev_indent
|
35
34
|
# This is *not* a control code, so we should close the previous one.
|
36
35
|
# Ignores newlines because they will be inserted after each line.
|
@@ -38,7 +37,7 @@ module Slim
|
|
38
37
|
prev_indent = false
|
39
38
|
end
|
40
39
|
|
41
|
-
result << compile(exp)
|
40
|
+
result << compile!(exp)
|
42
41
|
end
|
43
42
|
|
44
43
|
# The last line can be a control code too.
|
data/lib/slim/engine.rb
CHANGED
@@ -3,11 +3,16 @@ module Slim
|
|
3
3
|
# @api public
|
4
4
|
class Engine < Temple::Engine
|
5
5
|
use Slim::Parser, :file
|
6
|
+
filter :Debugger, :debug, :prefix => 'before end insertion'
|
6
7
|
use Slim::EndInserter
|
8
|
+
filter :Debugger, :debug, :prefix => 'after end insertion'
|
7
9
|
use Slim::EmbeddedEngine
|
8
|
-
use Slim::Compiler
|
9
|
-
|
10
|
-
|
10
|
+
use Slim::Compiler
|
11
|
+
filter :Debugger, :debug, :prefix => 'after compilation'
|
12
|
+
filter :EscapeHTML, :use_html_safe
|
13
|
+
use Temple::HTML::Pretty, :format, :attr_wrapper, :id_delimiter, :id_concat, :pretty,
|
14
|
+
:pretty => false, :attr_wrapper => '"', :format => :html5, :id_delimiter => nil
|
15
|
+
filter :Debugger, :debug, :prefix => 'after html'
|
11
16
|
filter :MultiFlattener
|
12
17
|
filter :StaticMerger
|
13
18
|
filter :DynamicInliner
|
data/lib/slim/filter.rb
CHANGED
@@ -1,48 +1,19 @@
|
|
1
1
|
module Slim
|
2
2
|
# Base class for Temple filters used in Slim
|
3
3
|
# @api private
|
4
|
-
class Filter
|
5
|
-
|
4
|
+
class Filter < Temple::Filter
|
5
|
+
temple_dispatch :slim
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(options = {})
|
10
|
-
@options = DEFAULT_OPTIONS.merge(options)
|
11
|
-
end
|
12
|
-
|
13
|
-
def compile(exp)
|
14
|
-
if exp[0] == :slim
|
15
|
-
_, type, *args = exp
|
16
|
-
else
|
17
|
-
type, *args = exp
|
18
|
-
end
|
19
|
-
|
20
|
-
if respond_to?("on_#{type}")
|
21
|
-
send("on_#{type}", *args)
|
22
|
-
else
|
23
|
-
exp
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def on_control(code, content)
|
28
|
-
[:slim, :control, code, compile(content)]
|
7
|
+
def on_slim_control(code, content)
|
8
|
+
[:slim, :control, code, compile!(content)]
|
29
9
|
end
|
30
10
|
|
31
|
-
def
|
32
|
-
[:slim, :
|
11
|
+
def on_slim_output(code, escape, content)
|
12
|
+
[:slim, :output, code, escape, compile!(content)]
|
33
13
|
end
|
34
14
|
|
35
|
-
def
|
36
|
-
[:
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Simple filter which prints Temple expression
|
41
|
-
# @api private
|
42
|
-
class Debugger < Filter
|
43
|
-
def compile(exp)
|
44
|
-
puts exp.inspect
|
45
|
-
exp
|
15
|
+
def on_slim_tag(name, attrs, closed, content)
|
16
|
+
[:slim, :tag, name, attrs, closed, compile!(content)]
|
46
17
|
end
|
47
18
|
end
|
48
19
|
end
|
data/lib/slim/parser.rb
CHANGED
@@ -2,6 +2,8 @@ module Slim
|
|
2
2
|
# Parses Slim code and transforms it to a Temple expression
|
3
3
|
# @api private
|
4
4
|
class Parser
|
5
|
+
include Temple::Mixins::Options
|
6
|
+
|
5
7
|
class SyntaxError < StandardError
|
6
8
|
attr_reader :error, :file, :line, :lineno, :column
|
7
9
|
|
@@ -22,11 +24,11 @@ module Slim
|
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
|
27
|
+
default_options[:tabsize] = 4
|
26
28
|
|
27
29
|
def initialize(options = {})
|
28
|
-
|
29
|
-
@tab
|
30
|
+
super
|
31
|
+
@tab = ' ' * @options[:tabsize]
|
30
32
|
end
|
31
33
|
|
32
34
|
# Compile string to Temple expression
|
@@ -72,8 +74,8 @@ module Slim
|
|
72
74
|
str.each_line do |line|
|
73
75
|
lineno += 1
|
74
76
|
|
75
|
-
# Remove the newline at the
|
76
|
-
line.
|
77
|
+
# Remove the newline at the end
|
78
|
+
line.chomp!
|
77
79
|
|
78
80
|
# Handle broken lines
|
79
81
|
if broken_line
|
@@ -177,24 +179,20 @@ module Slim
|
|
177
179
|
block << [:slim, :text, line.sub(/^( )/, '')]
|
178
180
|
text_indent = block_indent + ($1 ? 2 : 1)
|
179
181
|
end
|
180
|
-
when
|
181
|
-
# Found a
|
182
|
-
|
183
|
-
# First of all we need to push a exp into the stack. Anything
|
184
|
-
# indented deeper will be pushed into this exp. We'll include the
|
185
|
-
# same exp in the current-stack, which makes sure that it'll be
|
186
|
-
# included in the generated code.
|
182
|
+
when ?-
|
183
|
+
# Found a code block.
|
184
|
+
# We expect the line to be broken or the next line to be indented.
|
187
185
|
block = [:multi]
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
186
|
+
broken_line = line[1..-1].strip
|
187
|
+
stacks.last << [:slim, :control, broken_line, block]
|
188
|
+
stacks << block
|
189
|
+
when ?=
|
190
|
+
# Found a output bloc
|
191
|
+
# We expect the line to be broken or the next line to be indented.
|
192
|
+
block = [:multi]
|
193
|
+
escape = line[1] != ?=
|
194
|
+
broken_line = escape ? line[1..-1].strip : line[2..-1].strip
|
195
|
+
stacks.last << [:slim, :output, escape, broken_line, block]
|
198
196
|
stacks << block
|
199
197
|
when ?!
|
200
198
|
# Found a directive (currently only used for doctypes)
|
@@ -297,12 +295,12 @@ module Slim
|
|
297
295
|
if line[0, 1] == delimiter
|
298
296
|
line.slice!(0)
|
299
297
|
else
|
300
|
-
syntax_error! "Expected closing
|
298
|
+
syntax_error! "Expected closing delimiter #{delimiter}", orig_line, lineno, orig_line.size - line.size
|
301
299
|
end
|
302
300
|
end
|
303
301
|
|
304
302
|
content = [:multi]
|
305
|
-
tag = [:slim, :tag, tag, attributes, content]
|
303
|
+
tag = [:slim, :tag, tag, attributes, false, content]
|
306
304
|
|
307
305
|
if line =~ /^\s*=(=?)/
|
308
306
|
# Handle output code
|
@@ -310,6 +308,10 @@ module Slim
|
|
310
308
|
broken_line = $'.strip
|
311
309
|
content << [:slim, :output, $1 != '=', broken_line, block]
|
312
310
|
[tag, block, broken_line, nil]
|
311
|
+
elsif line =~ /^\s*\//
|
312
|
+
# Closed tag
|
313
|
+
tag[4] = true
|
314
|
+
[tag, block, nil, nil]
|
313
315
|
elsif !line.empty?
|
314
316
|
# Handle text content
|
315
317
|
content << [:slim, :text, line.sub(/^( )/, '')]
|
@@ -362,7 +364,7 @@ module Slim
|
|
362
364
|
|
363
365
|
# A little helper for raising exceptions.
|
364
366
|
def syntax_error!(message, *args)
|
365
|
-
raise SyntaxError.new(message,
|
367
|
+
raise SyntaxError.new(message, options[:file], *args)
|
366
368
|
end
|
367
369
|
end
|
368
370
|
end
|
data/lib/slim/template.rb
CHANGED
@@ -1,37 +1,8 @@
|
|
1
1
|
module Slim
|
2
2
|
# Tilt template implementation for Slim
|
3
3
|
# @api public
|
4
|
-
class Template <
|
5
|
-
|
6
|
-
#
|
7
|
-
# Called immediately after template data is loaded.
|
8
|
-
#
|
9
|
-
# @return [void]
|
10
|
-
def prepare
|
11
|
-
@src = Engine.new(options.merge(:file => eval_file)).compile(data)
|
12
|
-
end
|
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
|
23
|
-
def evaluate(scope, locals, &block)
|
24
|
-
scope.instance_eval { extend Slim::Helpers } if options[:helpers]
|
25
|
-
super
|
26
|
-
end
|
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
|
32
|
-
def precompiled_template(locals)
|
33
|
-
@src
|
34
|
-
end
|
4
|
+
class Template < Temple::Template
|
5
|
+
engine Slim::Engine
|
35
6
|
end
|
36
7
|
|
37
8
|
Tilt.register 'slim', Template
|
data/lib/slim/version.rb
CHANGED
data/slim.gemspec
CHANGED
@@ -20,8 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
|
-
s.add_runtime_dependency(%q<
|
24
|
-
s.add_runtime_dependency(%q<temple>, ["~> 0.1.3"])
|
23
|
+
s.add_runtime_dependency(%q<temple>, ["~> 0.1.4"])
|
25
24
|
s.add_runtime_dependency(%q<tilt>, ["~> 1.1"])
|
26
25
|
s.add_development_dependency(%q<rake>, [">= 0.8.7"])
|
27
26
|
s.add_development_dependency(%q<haml>, [">= 0"])
|
data/test/helper.rb
CHANGED
@@ -2,36 +2,40 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'minitest/unit'
|
5
|
+
require 'slim'
|
5
6
|
|
6
7
|
MiniTest::Unit.autorun
|
7
8
|
|
8
|
-
require File.dirname(__FILE__) + '/../lib/slim'
|
9
|
-
|
10
9
|
class TestSlim < MiniTest::Unit::TestCase
|
11
10
|
def setup
|
12
11
|
@env = Env.new
|
12
|
+
# Slim::Filter::DEFAULT_OPTIONS[:debug] = true
|
13
13
|
end
|
14
14
|
|
15
15
|
def teardown
|
16
16
|
String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?)
|
17
17
|
String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe)
|
18
18
|
Object.send(:undef_method, :html_safe?) if Object.method_defined?(:html_safe?)
|
19
|
-
|
19
|
+
Temple::Filters::EscapeHTML.default_options.delete(:use_html_safe)
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(source, options = {}, &block)
|
23
|
+
Slim::Template.new(options[:file], options) { source }.render(@env, &block)
|
20
24
|
end
|
21
25
|
|
22
|
-
def assert_html(expected, source, options = {})
|
23
|
-
assert_equal expected,
|
26
|
+
def assert_html(expected, source, options = {}, &block)
|
27
|
+
assert_equal expected, render(source, options, &block)
|
24
28
|
end
|
25
29
|
|
26
30
|
def assert_syntax_error(message, source, options = {})
|
27
|
-
|
31
|
+
render(source, options)
|
28
32
|
raise 'Syntax error expected'
|
29
33
|
rescue Slim::Parser::SyntaxError => ex
|
30
34
|
assert_equal message, ex.message
|
31
35
|
end
|
32
36
|
|
33
37
|
def assert_ruby_error(error, from, source, options = {})
|
34
|
-
|
38
|
+
render(source, options)
|
35
39
|
raise 'Ruby error expected'
|
36
40
|
rescue error => ex
|
37
41
|
ex.backtrace[0] =~ /^(.*?:\d+):/
|
@@ -39,15 +43,28 @@ class TestSlim < MiniTest::Unit::TestCase
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def assert_ruby_syntax_error(from, source, options = {})
|
42
|
-
|
46
|
+
render(source, options)
|
43
47
|
raise 'Ruby syntax error expected'
|
44
48
|
rescue SyntaxError => ex
|
45
49
|
ex.message =~ /^(.*?:\d+):/
|
46
50
|
assert_equal from, $1
|
47
51
|
end
|
52
|
+
|
53
|
+
def assert_runtime_error(message, source, options = {})
|
54
|
+
render(source, options)
|
55
|
+
raise Exception, 'Runtime error expected'
|
56
|
+
rescue RuntimeError => ex
|
57
|
+
assert_equal message, ex.message
|
58
|
+
end
|
48
59
|
end
|
49
60
|
|
50
61
|
class Env
|
62
|
+
attr_reader :var
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@var = 'instance'
|
66
|
+
end
|
67
|
+
|
51
68
|
def id_helper
|
52
69
|
"notice"
|
53
70
|
end
|
@@ -60,6 +77,16 @@ class Env
|
|
60
77
|
show
|
61
78
|
end
|
62
79
|
|
80
|
+
def define_macro(name, &block)
|
81
|
+
@macro ||= {}
|
82
|
+
@macro[name.to_s] = block
|
83
|
+
''
|
84
|
+
end
|
85
|
+
|
86
|
+
def call_macro(name, *args)
|
87
|
+
@macro[name.to_s].call(*args)
|
88
|
+
end
|
89
|
+
|
63
90
|
def hello_world(text = "Hello World from @env", opts = {})
|
64
91
|
text << opts.to_a * " " if opts.any?
|
65
92
|
if block_given?
|
@@ -15,12 +15,37 @@ p
|
|
15
15
|
source = %q{
|
16
16
|
p
|
17
17
|
= hello_world "Hello Ruby!" do
|
18
|
-
= hello_world "Hello from within a block!
|
18
|
+
= hello_world "Hello from within a block!"
|
19
19
|
}
|
20
20
|
|
21
|
-
assert_html '<p>Hello Ruby! Hello from within a block!
|
21
|
+
assert_html '<p>Hello Ruby! Hello from within a block! Hello Ruby!</p>', source
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_render_with_output_code_within_block_2
|
25
|
+
source = %q{
|
26
|
+
p
|
27
|
+
= hello_world "Hello Ruby!" do
|
28
|
+
= hello_world "Hello from within a block!" do
|
29
|
+
= hello_world "And another one!"
|
30
|
+
}
|
31
|
+
|
32
|
+
assert_html '<p>Hello Ruby! Hello from within a block! And another one! Hello from within a block! Hello Ruby!</p>', source
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_output_block_with_arguments
|
36
|
+
source = %q{
|
37
|
+
p
|
38
|
+
= define_macro :person do |first_name, last_name|
|
39
|
+
.first_name = first_name
|
40
|
+
.last_name = last_name
|
41
|
+
== call_macro :person, 'John', 'Doe'
|
42
|
+
== call_macro :person, 'Max', 'Mustermann'
|
43
|
+
}
|
44
|
+
|
45
|
+
assert_html '<p><div class="first_name">John</div><div class="last_name">Doe</div><div class="first_name">Max</div><div class="last_name">Mustermann</div></p>', source
|
46
|
+
end
|
47
|
+
|
48
|
+
|
24
49
|
def test_render_with_control_code_loop
|
25
50
|
source = %q{
|
26
51
|
p
|
@@ -30,4 +55,14 @@ p
|
|
30
55
|
|
31
56
|
assert_html '<p>Hey!Hey!Hey!</p>', source
|
32
57
|
end
|
58
|
+
|
59
|
+
def test_captured_code_block_with_conditional
|
60
|
+
source = %q{
|
61
|
+
= hello_world "Hello Ruby!" do
|
62
|
+
- if true
|
63
|
+
| Hello from within a block!
|
64
|
+
}
|
65
|
+
|
66
|
+
assert_html 'Hello Ruby! Hello from within a block! Hello Ruby!', source
|
67
|
+
end
|
33
68
|
end
|
@@ -47,7 +47,7 @@ p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
|
|
47
47
|
|
48
48
|
def test_render_with_global_html_safe_false
|
49
49
|
String.send(:define_method, :html_safe?) { false }
|
50
|
-
|
50
|
+
Temple::Filters::EscapeHTML.default_options[:use_html_safe] = false
|
51
51
|
|
52
52
|
source = %q{
|
53
53
|
p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
|
@@ -58,7 +58,7 @@ p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
|
|
58
58
|
|
59
59
|
def test_render_with_global_html_safe_true
|
60
60
|
String.send(:define_method, :html_safe?) { true }
|
61
|
-
|
61
|
+
Temple::Filters::EscapeHTML.default_options[:use_html_safe] = true
|
62
62
|
|
63
63
|
source = %q{
|
64
64
|
p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
|