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/{vim → extra}/slim.vim
RENAMED
File without changes
|
data/extra/test.slim
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
! doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
title Slim Test
|
5
|
+
meta name="keywords" content="slim, syntax"
|
6
|
+
|
7
|
+
javascript:
|
8
|
+
$(function() {
|
9
|
+
alert('Hello World');
|
10
|
+
});
|
11
|
+
|
12
|
+
body
|
13
|
+
/ comment block
|
14
|
+
with multiple lines
|
15
|
+
|
16
|
+
h1 = @page_title
|
17
|
+
p#notice.message
|
18
|
+
| Welcome to the the syntax test.
|
19
|
+
This file is to excercise the various markup.
|
20
|
+
|
21
|
+
- unless @users.empty?
|
22
|
+
table
|
23
|
+
- for user in users do
|
24
|
+
tr
|
25
|
+
td.user id=user.name = user.name
|
26
|
+
- else
|
27
|
+
p There are no users.
|
28
|
+
|
29
|
+
#content Hello #{@user.name}! Welcome to the test page!
|
30
|
+
Try out Slim!
|
31
|
+
|
32
|
+
= function_with_many_parameters(:a, \
|
33
|
+
variable, :option => 1)
|
34
|
+
|
35
|
+
p.text
|
36
|
+
' Another text block
|
37
|
+
with multiple lines
|
38
|
+
|
39
|
+
.text#footer
|
40
|
+
` Footer text block
|
41
|
+
with multiple lines
|
data/lib/slim.rb
CHANGED
@@ -11,6 +11,7 @@ require 'slim/embedded_engine'
|
|
11
11
|
require 'slim/compiler'
|
12
12
|
require 'slim/engine'
|
13
13
|
require 'slim/template'
|
14
|
+
require 'slim/version'
|
14
15
|
|
15
16
|
begin
|
16
17
|
require 'escape_utils'
|
@@ -19,6 +20,6 @@ end
|
|
19
20
|
|
20
21
|
module Slim
|
21
22
|
def self.version
|
22
|
-
|
23
|
+
VERSION
|
23
24
|
end
|
24
25
|
end
|
data/lib/slim/command.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'slim'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Slim
|
5
|
+
# Slim commandline interface
|
6
|
+
# @api private
|
7
|
+
class Command
|
8
|
+
def initialize(args)
|
9
|
+
@args = args
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Run command
|
14
|
+
def run
|
15
|
+
@opts = OptionParser.new(&method(:set_opts))
|
16
|
+
@opts.parse!(@args)
|
17
|
+
process
|
18
|
+
exit 0
|
19
|
+
rescue Exception => ex
|
20
|
+
raise ex if @options[:trace] || SystemExit === ex
|
21
|
+
$stderr.print "#{ex.class}: " if ex.class != RuntimeError
|
22
|
+
$stderr.puts ex.message
|
23
|
+
$stderr.puts ' Use --trace for backtrace.'
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Configure OptionParser
|
30
|
+
def set_opts(opts)
|
31
|
+
opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
|
32
|
+
@options[:input] = $stdin
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on('--trace', :NONE, 'Show a full traceback on error') do
|
36
|
+
@options[:trace] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on('-c', '--compile', :NONE, 'Compile only but do not run') do
|
40
|
+
@options[:compile] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on_tail('-v', '--version', 'Print version') do
|
49
|
+
puts "Slim #{Slim.version}"
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Process command
|
55
|
+
def process
|
56
|
+
args = @args.dup
|
57
|
+
unless @options[:input]
|
58
|
+
file = args.shift
|
59
|
+
if file
|
60
|
+
@options[:file] = file
|
61
|
+
@options[:input] = File.open(file, 'r')
|
62
|
+
else
|
63
|
+
@options[:file] = 'STDIN'
|
64
|
+
@options[:input] = $stdin
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
unless @options[:output]
|
69
|
+
file = args.shift
|
70
|
+
@options[:output] = file ? File.open(file, 'w') : $stdout
|
71
|
+
end
|
72
|
+
|
73
|
+
if @options[:compile]
|
74
|
+
@options[:output].puts(Slim::Engine.new(:file => @options[:file]).compile(@options[:input].read))
|
75
|
+
else
|
76
|
+
@options[:output].puts(Slim::Template.new(@options[:file]) { @options[:input].read }.render)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/slim/compiler.rb
CHANGED
@@ -1,33 +1,64 @@
|
|
1
1
|
module Slim
|
2
2
|
# Compiles Slim expressions into Temple::HTML expressions.
|
3
|
+
# @api private
|
3
4
|
class Compiler < Filter
|
5
|
+
# Handle text expression `[:slim, :text, string]`
|
6
|
+
#
|
7
|
+
# @param [String] string Static text
|
8
|
+
# @return [Array] Compiled temple expression
|
4
9
|
def on_text(string)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
# Interpolate variables in text (#{variable}).
|
11
|
+
# Split the text into multiple dynamic and static parts.
|
12
|
+
block = [:multi]
|
13
|
+
until string.empty?
|
14
|
+
case string
|
15
|
+
when /^\\(\#\{[^\}]*\})/
|
16
|
+
# Escaped interpolation
|
17
|
+
block << [:static, $1]
|
18
|
+
when /^\#\{([^\}]*)\}/
|
19
|
+
# Interpolation
|
20
|
+
block << [:dynamic, escape_code($1)]
|
21
|
+
when /^([^\#]+|\#)/
|
22
|
+
# Static text
|
23
|
+
block << [:static, $&]
|
24
|
+
end
|
25
|
+
string = $'
|
9
26
|
end
|
27
|
+
block
|
10
28
|
end
|
11
29
|
|
30
|
+
# Handle control expression `[:slim, :control, code, content]`
|
31
|
+
#
|
32
|
+
# @param [String] ruby code
|
33
|
+
# @param [Array] content Temple expression
|
34
|
+
# @return [Array] Compiled temple expression
|
12
35
|
def on_control(code, content)
|
13
36
|
[:multi,
|
14
37
|
[:block, code],
|
15
38
|
compile(content)]
|
16
39
|
end
|
17
40
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
41
|
+
# Handle output expression `[:slim, :output, escape, code, content]`
|
42
|
+
#
|
43
|
+
# @param [Boolean] escape Escape html
|
44
|
+
# @param [String] code Ruby code
|
45
|
+
# @param [Array] content Temple expression
|
46
|
+
# @return [Array] Compiled temple expression
|
23
47
|
def on_output(escape, code, content)
|
24
48
|
if empty_exp?(content)
|
25
|
-
[:dynamic, escape ? escape_code(code) : code]
|
49
|
+
[:multi, [:dynamic, escape ? escape_code(code) : code], content]
|
26
50
|
else
|
27
51
|
on_output_block(escape, code, content)
|
28
52
|
end
|
29
53
|
end
|
30
54
|
|
55
|
+
# Handle output expression `[:slim, :output, escape, code, content]`
|
56
|
+
# if content is not empty.
|
57
|
+
#
|
58
|
+
# @param [Boolean] escape Escape html
|
59
|
+
# @param [String] code Ruby code
|
60
|
+
# @param [Array] content Temple expression
|
61
|
+
# @return [Array] Compiled temple expression
|
31
62
|
def on_output_block(escape, code, content)
|
32
63
|
tmp1, tmp2 = tmp_var, tmp_var
|
33
64
|
|
@@ -47,28 +78,36 @@ module Slim
|
|
47
78
|
[:block, tmp2],
|
48
79
|
|
49
80
|
# Close the block.
|
50
|
-
[:block,
|
81
|
+
[:block, 'end'],
|
51
82
|
|
52
83
|
# Output the content.
|
53
84
|
on_output(escape, tmp1, [:multi])]
|
54
85
|
end
|
55
86
|
|
87
|
+
# Handle directive expression `[:slim, :directive, type]`
|
88
|
+
#
|
89
|
+
# @param [String] type Directive type
|
90
|
+
# @return [Array] Compiled temple expression
|
56
91
|
def on_directive(type)
|
57
|
-
|
58
|
-
when /^doctype/
|
92
|
+
if type =~ /^doctype/
|
59
93
|
[:html, :doctype, $'.strip]
|
60
|
-
else
|
61
94
|
end
|
62
95
|
end
|
63
96
|
|
97
|
+
# Handle tag expression `[:slim, :tag, name, attrs, content]`
|
98
|
+
#
|
99
|
+
# @param [String] name Tag name
|
100
|
+
# @param [Array] attrs Attributes
|
101
|
+
# @param [Array] content Temple expression
|
102
|
+
# @return [Array] Compiled temple expression
|
64
103
|
def on_tag(name, attrs, content)
|
65
|
-
attrs = attrs.inject([:html, :attrs]) do |m, (key, value)|
|
66
|
-
if
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
m << [:html, :basicattr, [:static, key], value]
|
104
|
+
attrs = attrs.inject([:html, :attrs]) do |m, (key, dynamic, value)|
|
105
|
+
value = if dynamic
|
106
|
+
[:dynamic, escape_code(value)]
|
107
|
+
else
|
108
|
+
on_text(value)
|
109
|
+
end
|
110
|
+
m << [:html, :basicattr, [:static, key.to_s], value]
|
72
111
|
end
|
73
112
|
|
74
113
|
[:html, :tag, name, attrs, compile(content)]
|
@@ -76,17 +115,17 @@ module Slim
|
|
76
115
|
|
77
116
|
private
|
78
117
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
'"%s"' % string
|
84
|
-
end
|
85
|
-
|
118
|
+
# Generate code to escape html
|
119
|
+
#
|
120
|
+
# @param [String] param Ruby code
|
121
|
+
# @return [String] Ruby code
|
86
122
|
def escape_code(param)
|
87
123
|
"Slim::Helpers.escape_html#{@options[:use_html_safe] ? '_safe' : ''}((#{param}))"
|
88
124
|
end
|
89
125
|
|
126
|
+
# Generate unique temporary variable name
|
127
|
+
#
|
128
|
+
# @return [String] Variable name
|
90
129
|
def tmp_var
|
91
130
|
@tmp_var ||= 0
|
92
131
|
"_slimtmp#{@tmp_var += 1}"
|
data/lib/slim/embedded_engine.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Slim
|
2
|
-
|
2
|
+
# Temple filter which processes embedded engines
|
3
|
+
# @api private
|
4
|
+
class EmbeddedEngine < Filter
|
3
5
|
@engines = {}
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(options)
|
8
|
-
@options = options
|
7
|
+
def self.register(name, klass, options = {})
|
8
|
+
@engines[name.to_s] = klass.new(options)
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.[](name)
|
@@ -14,9 +14,8 @@ module Slim
|
|
14
14
|
engine.dup
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
@engines[name.to_s] = klass.new(options)
|
17
|
+
def on_embedded(engine, *body)
|
18
|
+
EmbeddedEngine[engine].on_embedded(engine, *body)
|
20
19
|
end
|
21
20
|
|
22
21
|
def collect_text(body)
|
@@ -34,20 +33,20 @@ module Slim
|
|
34
33
|
# Code to collect local variables
|
35
34
|
COLLECT_LOCALS = %q{eval('{' + local_variables.select {|v| v[0] != ?_ }.map {|v| ":#{v}=>#{v}" }.join(',') + '}')}
|
36
35
|
|
37
|
-
def
|
36
|
+
def on_embedded(engine, *body)
|
38
37
|
text = collect_text(body)
|
39
|
-
engine = Tilt[
|
40
|
-
if options[:precompiled]
|
38
|
+
engine = Tilt[engine]
|
39
|
+
if @options[:precompiled]
|
41
40
|
# Wrap precompiled code in proc, local variables from out the proc are accessible
|
42
41
|
# WARNING: This is a bit of a hack. Tilt::Engine#precompiled is protected
|
43
42
|
precompiled = engine.new { text }.send(:precompiled, {}).first
|
44
43
|
[:dynamic, "proc { #{precompiled} }.call"]
|
45
|
-
elsif options[:dynamic]
|
44
|
+
elsif @options[:dynamic]
|
46
45
|
# Fully dynamic evaluation of the template during runtime (Slow and uncached)
|
47
46
|
[:dynamic, "#{engine.name}.new { #{text.inspect} }.render(self, #{COLLECT_LOCALS})"]
|
48
|
-
elsif options[:interpolate]
|
47
|
+
elsif @options[:interpolate]
|
49
48
|
# Static template with interpolated ruby code
|
50
|
-
[:
|
49
|
+
[:slim, :text, engine.new { text }.render]
|
51
50
|
else
|
52
51
|
# Static template
|
53
52
|
[:static, engine.new { text }.render]
|
@@ -56,23 +55,14 @@ module Slim
|
|
56
55
|
end
|
57
56
|
|
58
57
|
class TagEngine < EmbeddedEngine
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def compile(body)
|
66
|
-
attrs = [:html, :attrs]
|
67
|
-
options[:attributes].each do |key, value|
|
68
|
-
attrs << [:html, :basicattr, [:static, key.to_s], [:static, value.to_s]]
|
69
|
-
end
|
70
|
-
[:html, :tag, options[:tag], attrs, compile_text(body)]
|
58
|
+
def on_embedded(engine, *body)
|
59
|
+
content = @options[:engine] ? @options[:engine].new.on_embedded(engine, *body) : [:multi, *body]
|
60
|
+
[:slim, :tag, @options[:tag], @options[:attributes].map {|k, v| [k, false, v ] }, content]
|
71
61
|
end
|
72
62
|
end
|
73
63
|
|
74
64
|
class RubyEngine < EmbeddedEngine
|
75
|
-
def
|
65
|
+
def on_embedded(engine, *body)
|
76
66
|
[:block, collect_text(body)]
|
77
67
|
end
|
78
68
|
end
|
@@ -83,9 +73,10 @@ module Slim
|
|
83
73
|
register :rdoc, TiltEngine, :interpolate => true
|
84
74
|
|
85
75
|
# These engines are executed at compile time
|
86
|
-
register :
|
87
|
-
register :
|
88
|
-
register :
|
76
|
+
register :coffee, TagEngine, :tag => 'script', :attributes => { :type => 'text/javascript' }, :engine => TiltEngine
|
77
|
+
register :sass, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
|
78
|
+
register :scss, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
|
79
|
+
register :less, TagEngine, :tag => 'style', :attributes => { :type => 'text/css' }, :engine => TiltEngine
|
89
80
|
|
90
81
|
# These engines are precompiled, code is embedded
|
91
82
|
register :erb, TiltEngine, :precompiled => true
|
data/lib/slim/end_inserter.rb
CHANGED
@@ -7,10 +7,13 @@ module Slim
|
|
7
7
|
# However, the parser is not smart enough (and that's a good thing) to
|
8
8
|
# automatically insert end's where they are needed. Luckily, this filter
|
9
9
|
# does *exactly* that (and it does it well!)
|
10
|
+
#
|
11
|
+
# @api private
|
10
12
|
class EndInserter < Filter
|
11
13
|
ELSE_REGEX = /^(else|elsif|when|end)\b/
|
12
14
|
END_REGEX = /^end\b/
|
13
15
|
|
16
|
+
# Handle multi expression `[:multi, *exps]`
|
14
17
|
def on_multi(*exps)
|
15
18
|
result = [:multi]
|
16
19
|
# This variable is true if the previous line was
|
data/lib/slim/engine.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module Slim
|
2
|
+
# Slim engine which transforms slim code to executable ruby code
|
3
|
+
# @api public
|
2
4
|
class Engine < Temple::Engine
|
3
|
-
use Slim::Parser
|
5
|
+
use Slim::Parser, :file
|
4
6
|
use Slim::EndInserter
|
7
|
+
use Slim::EmbeddedEngine
|
5
8
|
use Slim::Compiler, :use_html_safe
|
6
9
|
#use Slim::Debugger
|
7
10
|
use Temple::HTML::Fast, :format, :attr_wrapper => '"', :format => :html5
|
@@ -9,13 +12,5 @@ module Slim
|
|
9
12
|
filter :StaticMerger
|
10
13
|
filter :DynamicInliner
|
11
14
|
generator :ArrayBuffer
|
12
|
-
|
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
|
19
|
-
end
|
20
15
|
end
|
21
16
|
end
|
data/lib/slim/filter.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Slim
|
2
|
+
# Base class for Temple filters used in Slim
|
3
|
+
# @api private
|
2
4
|
class Filter
|
3
5
|
include Temple::Utils
|
4
6
|
|
@@ -35,6 +37,8 @@ module Slim
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
40
|
+
# Simple filter which prints Temple expression
|
41
|
+
# @api private
|
38
42
|
class Debugger < Filter
|
39
43
|
def compile(exp)
|
40
44
|
puts exp.inspect
|
data/lib/slim/helpers.rb
CHANGED
@@ -1,32 +1,76 @@
|
|
1
1
|
module Slim
|
2
|
+
# Slim helper functions
|
3
|
+
#
|
4
|
+
# @api public
|
2
5
|
module Helpers
|
6
|
+
# Iterate over `Enumerable` object
|
7
|
+
# yielding each element to a Slim block
|
8
|
+
# and putting the result into `<li>` elements.
|
9
|
+
# For example:
|
10
|
+
#
|
11
|
+
# = list_of([1,2]) do |i|
|
12
|
+
# = i
|
13
|
+
#
|
14
|
+
# Produces:
|
15
|
+
#
|
16
|
+
# <li>1</li>
|
17
|
+
# <li>2</li>
|
18
|
+
#
|
19
|
+
# @param enum [Enumerable] The enumerable objects to iterate over
|
20
|
+
# @yield [item] A block which contains Slim code that goes within list items
|
21
|
+
# @yieldparam item An element of `enum`
|
22
|
+
# @api public
|
3
23
|
def list_of(enum, &block)
|
4
|
-
enum.map do |i|
|
24
|
+
list = enum.map do |i|
|
5
25
|
"<li>#{yield(i)}</li>"
|
6
26
|
end.join("\n")
|
27
|
+
list.respond_to?(:html_safe) ? list.html_safe : list
|
7
28
|
end
|
8
29
|
|
30
|
+
# Returns an escaped copy of `html`.
|
31
|
+
# Strings which are declared as html_safe are not escaped.
|
32
|
+
#
|
33
|
+
# @param html [String] The string to escape
|
34
|
+
# @return [String] The escaped string
|
35
|
+
# @api public
|
9
36
|
def escape_html_safe(html)
|
10
37
|
html.html_safe? ? html : escape_html(html)
|
11
38
|
end
|
12
39
|
|
13
40
|
if defined?(EscapeUtils)
|
41
|
+
# Returns an escaped copy of `html`.
|
42
|
+
#
|
43
|
+
# @param html [String] The string to escape
|
44
|
+
# @return [String] The escaped string
|
45
|
+
# @api public
|
14
46
|
def escape_html(html)
|
15
47
|
EscapeUtils.escape_html(html.to_s)
|
16
48
|
end
|
17
49
|
elsif RUBY_VERSION > '1.9'
|
50
|
+
# Used by escape_html
|
51
|
+
# @api private
|
18
52
|
ESCAPE_HTML = {
|
19
53
|
'&' => '&',
|
20
54
|
'"' => '"',
|
21
55
|
'<' => '<',
|
22
56
|
'>' => '>',
|
23
57
|
'/' => '/',
|
24
|
-
}
|
58
|
+
}.freeze
|
25
59
|
|
60
|
+
# Returns an escaped copy of `html`.
|
61
|
+
#
|
62
|
+
# @param html [String] The string to escape
|
63
|
+
# @return [String] The escaped string
|
64
|
+
# @api public
|
26
65
|
def escape_html(html)
|
27
66
|
html.to_s.gsub(/[&\"<>\/]/, ESCAPE_HTML)
|
28
67
|
end
|
29
68
|
else
|
69
|
+
# Returns an escaped copy of `html`.
|
70
|
+
#
|
71
|
+
# @param html [String] The string to escape
|
72
|
+
# @return [String] The escaped string
|
73
|
+
# @api public
|
30
74
|
def escape_html(html)
|
31
75
|
html.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<').gsub(/\//, '/')
|
32
76
|
end
|