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/{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
|