slim 1.3.0 → 1.3.2

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.
Files changed (52) hide show
  1. data/.travis.yml +9 -5
  2. data/CHANGES +23 -0
  3. data/Gemfile +13 -2
  4. data/README.md +346 -105
  5. data/Rakefile +21 -9
  6. data/lib/slim.rb +3 -3
  7. data/lib/slim/boolean_attributes.rb +67 -0
  8. data/lib/slim/command.rb +23 -23
  9. data/lib/slim/control_structures.rb +57 -0
  10. data/lib/slim/embedded_engine.rb +80 -43
  11. data/lib/slim/end_inserter.rb +1 -1
  12. data/lib/slim/engine.rb +21 -15
  13. data/lib/slim/filter.rb +0 -6
  14. data/lib/slim/grammar.rb +2 -7
  15. data/lib/slim/interpolation.rb +1 -1
  16. data/lib/slim/logic_less/filter.rb +8 -8
  17. data/lib/slim/logic_less/wrapper.rb +1 -1
  18. data/lib/slim/parser.rb +51 -52
  19. data/lib/slim/splat_attributes.rb +112 -0
  20. data/lib/slim/translator.rb +13 -12
  21. data/lib/slim/version.rb +1 -1
  22. data/slim.gemspec +1 -1
  23. data/test/{slim → core}/helper.rb +3 -7
  24. data/test/{slim → core}/test_code_blocks.rb +0 -0
  25. data/test/{slim → core}/test_code_escaping.rb +4 -4
  26. data/test/{slim → core}/test_code_evaluation.rb +3 -112
  27. data/test/{slim → core}/test_code_output.rb +0 -0
  28. data/test/{slim → core}/test_code_structure.rb +0 -0
  29. data/test/{slim → core}/test_embedded_engines.rb +8 -3
  30. data/test/{slim → core}/test_encoding.rb +0 -0
  31. data/test/core/test_html_attributes.rb +218 -0
  32. data/test/{slim → core}/test_html_escaping.rb +17 -0
  33. data/test/{slim → core}/test_html_structure.rb +13 -98
  34. data/test/{slim → core}/test_parser_errors.rb +24 -15
  35. data/test/{slim → core}/test_pretty.rb +0 -0
  36. data/test/{slim → core}/test_ruby_errors.rb +7 -0
  37. data/test/{slim → core}/test_slim_template.rb +0 -0
  38. data/test/{slim → core}/test_text_interpolation.rb +2 -2
  39. data/test/core/test_thread_options.rb +18 -0
  40. data/test/{slim/logic_less → logic_less}/test_logic_less.rb +21 -0
  41. data/test/{slim/logic_less → logic_less}/test_wrapper.rb +3 -3
  42. data/test/rails/app/controllers/slim_controller.rb +4 -2
  43. data/test/rails/app/views/parents/_form.html.slim +1 -0
  44. data/test/rails/app/views/parents/edit.html.slim +2 -1
  45. data/test/rails/app/views/parents/new.html.slim +2 -1
  46. data/test/rails/app/views/slim/thread_options.html.slim +1 -0
  47. data/test/rails/test/test_slim.rb +6 -4
  48. data/test/{slim/translator → translator}/test_translator.rb +0 -0
  49. metadata +44 -82
  50. data/lib/slim/compiler.rb +0 -194
  51. data/test/rails/app/views/slim/nil.html.slim +0 -1
  52. data/test/slim/test_chain_manipulation.rb +0 -42
data/Rakefile CHANGED
@@ -11,24 +11,26 @@ task :bench, :iterations, :slow do
11
11
  ruby('benchmarks/run-benchmarks.rb')
12
12
  end
13
13
 
14
- task 'test' => %w(test:core test:logic_less test:translator)
14
+ task 'test' => %w(test:core_and_plugins)
15
15
 
16
16
  namespace 'test' do
17
+ task 'core_and_plugins' => %w(core logic_less translator)
18
+
17
19
  Rake::TestTask.new('core') do |t|
18
- t.libs << 'lib' << 'test/slim'
19
- t.test_files = FileList['test/slim/test_*.rb']
20
+ t.libs << 'lib' << 'test/core'
21
+ t.test_files = FileList['test/core/test_*.rb']
20
22
  t.verbose = true
21
23
  end
22
24
 
23
25
  Rake::TestTask.new('logic_less') do |t|
24
- t.libs << 'lib' << 'test/slim'
25
- t.test_files = FileList['test/slim/logic_less/test_*.rb']
26
+ t.libs << 'lib' << 'test/core'
27
+ t.test_files = FileList['test/logic_less/test_*.rb']
26
28
  t.verbose = true
27
29
  end
28
30
 
29
31
  Rake::TestTask.new('translator') do |t|
30
- t.libs << 'lib' << 'test/slim'
31
- t.test_files = FileList['test/slim/translator/test_*.rb']
32
+ t.libs << 'lib' << 'test/core'
33
+ t.test_files = FileList['test/translator/test_*.rb']
32
34
  t.verbose = true
33
35
  end
34
36
 
@@ -38,8 +40,18 @@ namespace 'test' do
38
40
  t.verbose = true
39
41
  end
40
42
 
41
- task 'ci' do |t|
42
- Rake::Task[ENV['TASK']].execute
43
+ begin
44
+ require 'sinatra'
45
+ spec = Gem::Specification.find_by_name('sinatra')
46
+ Rake::TestTask.new('sinatra') do |t|
47
+ # Run Slim integration test in Sinatra
48
+ t.test_files = FileList["#{spec.gem_dir}/test/slim_test.rb"]
49
+ t.verbose = true
50
+ end
51
+ rescue LoadError
52
+ task :sinatra do
53
+ abort 'Sinatra is not available'
54
+ end
43
55
  end
44
56
  end
45
57
 
@@ -1,12 +1,12 @@
1
- # encoding: utf-8
2
-
3
1
  require 'temple'
4
2
  require 'slim/parser'
5
3
  require 'slim/filter'
6
4
  require 'slim/end_inserter'
7
5
  require 'slim/embedded_engine'
8
6
  require 'slim/interpolation'
9
- require 'slim/compiler'
7
+ require 'slim/control_structures'
8
+ require 'slim/splat_attributes'
9
+ require 'slim/boolean_attributes'
10
10
  require 'slim/engine'
11
11
  require 'slim/template'
12
12
  require 'slim/version'
@@ -0,0 +1,67 @@
1
+ module Slim
2
+ # @api private
3
+ class BooleanAttributes < Filter
4
+ define_options :attr_delimiter
5
+
6
+ # Handle attributes expression `[:html, :attrs, *attrs]`
7
+ #
8
+ # @param [Array] attrs Array of temple expressions
9
+ # @return [Array] Compiled temple expression
10
+ def on_html_attrs(*attrs)
11
+ [:multi, *attrs.map {|a| compile(a) }]
12
+ end
13
+
14
+ # Handle attribute expression `[:slim, :attr, escape, code]`
15
+ #
16
+ # @param [Boolean] escape Escape html
17
+ # @param [String] code Ruby code
18
+ # @return [Array] Compiled temple expression
19
+ def on_html_attr(name, value)
20
+ unless value[0] == :slim && value[1] == :attrvalue
21
+ @attr = name
22
+ return super
23
+ end
24
+
25
+ escape, code = value[2], value[3]
26
+ case code
27
+ when 'true'
28
+ [:html, :attr, name, [:static, name]]
29
+ when 'false', 'nil'
30
+ [:multi]
31
+ else
32
+ tmp = unique_name
33
+ conds = [:case, tmp,
34
+ ['true', [:html, :attr, name, [:static, name]]],
35
+ ['false, nil', [:multi]]]
36
+ if delimiter = options[:attr_delimiter][name]
37
+ conds << ['Array',
38
+ [:multi,
39
+ [:code, "#{tmp} = #{tmp}.flatten.compact.join(#{delimiter.inspect})"],
40
+ [:if, "!#{tmp}.empty?",
41
+ [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]]]
42
+ end
43
+ conds << [:else, [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]
44
+ [:multi, [:code, "#{tmp} = (#{code})"], conds]
45
+ end
46
+ end
47
+
48
+ # Handle attribute expression `[:slim, :attrvalue, escape, code]`
49
+ #
50
+ # @param [Boolean] escape Escape html
51
+ # @param [String] code Ruby code
52
+ # @return [Array] Compiled temple expression
53
+ def on_slim_attrvalue(escape, code)
54
+ tmp = unique_name
55
+ [:multi,
56
+ [:code, "#{tmp} = #{code}"],
57
+ [:escape, escape,
58
+ [:dynamic,
59
+ if delimiter = options[:attr_delimiter][@attr]
60
+ "Array === #{tmp} ? #{tmp}.flatten.compact.join(#{delimiter.inspect}) : #{tmp}"
61
+ else
62
+ tmp
63
+ end
64
+ ]]]
65
+ end
66
+ end
67
+ end
@@ -3,6 +3,10 @@ require 'slim/translator'
3
3
  require 'optparse'
4
4
 
5
5
  module Slim
6
+ Engine.set_default_options :logic_less => false,
7
+ :pretty => false,
8
+ :tr => false
9
+
6
10
  # Slim commandline interface
7
11
  # @api private
8
12
  class Command
@@ -29,32 +33,38 @@ module Slim
29
33
 
30
34
  # Configure OptionParser
31
35
  def set_opts(opts)
32
- opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
36
+ opts.on('-s', '--stdin', 'Read input from standard input instead of an input file') do
33
37
  @options[:input] = $stdin
34
38
  end
35
39
 
36
- opts.on('--trace', :NONE, 'Show a full traceback on error') do
40
+ opts.on('--trace', 'Show a full traceback on error') do
37
41
  @options[:trace] = true
38
42
  end
39
43
 
40
- opts.on('-c', '--compile', :NONE, 'Compile only but do not run') do
44
+ opts.on('-c', '--compile', 'Compile only but do not run') do
45
+ @options[:compile] = true
46
+ end
47
+
48
+ opts.on('-r', '--rails', 'Generate rails compatible code (Implies --compile)') do
49
+ Engine.set_default_options :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer
41
50
  @options[:compile] = true
42
51
  end
43
52
 
44
- opts.on('-r', '--rails', :NONE, 'Generate rails compatible code (combine with -c)') do
45
- @options[:rails] = true
53
+ opts.on('-t', '--translator', 'Enable translator plugin') do
54
+ Engine.set_default_options :tr => true
46
55
  end
47
56
 
48
- opts.on('-t', '--translator', :NONE, 'Enable translator plugin') do
49
- @options[:translator] = true
57
+ opts.on('-l', '--logic-less', 'Enable logic less plugin') do
58
+ Engine.set_default_options :logic_less => true
50
59
  end
51
60
 
52
- opts.on('-l', '--logic-less', :NONE, 'Enable logic-less plugin') do
53
- @options[:logic_less] = true
61
+ opts.on('-p', '--pretty', 'Produce pretty html') do
62
+ Engine.set_default_options :pretty => true
54
63
  end
55
64
 
56
- opts.on('-p', '--pretty', :NONE, 'Produce pretty html') do
57
- @options[:pretty] = true
65
+ opts.on('-o', '--option [NAME=CODE]', String, 'Set slim option') do |str|
66
+ parts = str.split('=', 2)
67
+ Engine.default_options[parts.first.to_sym] = eval(parts.last)
58
68
  end
59
69
 
60
70
  opts.on_tail('-h', '--help', 'Show this message') do
@@ -88,19 +98,9 @@ module Slim
88
98
  end
89
99
 
90
100
  if @options[:compile]
91
- @options[:output].puts(Slim::Engine.new(:file => @options[:file],
92
- :pretty => @options[:pretty],
93
- :logic_less => @options[:logic_less],
94
- :disable_capture => @options[:rails],
95
- :tr => @options[:translator],
96
- :generator => @options[:rails] ?
97
- Temple::Generators::RailsOutputBuffer :
98
- Temple::Generators::ArrayBuffer).call(@options[:input].read))
101
+ @options[:output].puts(Slim::Engine.new(:file => @options[:file]).call(@options[:input].read))
99
102
  else
100
- @options[:output].puts(Slim::Template.new(@options[:file],
101
- :pretty => @options[:pretty],
102
- :tr => @options[:translator],
103
- :logic_less => @options[:logic_less]) { @options[:input].read }.render)
103
+ @options[:output].puts(Slim::Template.new(@options[:file]) { @options[:input].read }.render)
104
104
  end
105
105
  end
106
106
  end
@@ -0,0 +1,57 @@
1
+ module Slim
2
+ # @api private
3
+ class ControlStructures < Filter
4
+ define_options :disable_capture
5
+
6
+ # Handle control expression `[:slim, :control, code, content]`
7
+ #
8
+ # @param [String] code Ruby code
9
+ # @param [Array] content Temple expression
10
+ # @return [Array] Compiled temple expression
11
+ def on_slim_control(code, content)
12
+ [:multi,
13
+ [:code, code],
14
+ compile(content)]
15
+ end
16
+
17
+ # Handle output expression `[:slim, :output, escape, code, content]`
18
+ #
19
+ # @param [Boolean] escape Escape html
20
+ # @param [String] code Ruby code
21
+ # @param [Array] content Temple expression
22
+ # @return [Array] Compiled temple expression
23
+ def on_slim_output(escape, code, content)
24
+ if empty_exp?(content)
25
+ [:multi, [:escape, escape, [:dynamic, code]], content]
26
+ else
27
+ tmp = unique_name
28
+
29
+ [:multi,
30
+ # Capture the result of the code in a variable. We can't do
31
+ # `[:dynamic, code]` because it's probably not a complete
32
+ # expression (which is a requirement for Temple).
33
+ [:block, "#{tmp} = #{code}",
34
+
35
+ # Capture the content of a block in a separate buffer. This means
36
+ # that `yield` will not output the content to the current buffer,
37
+ # but rather return the output.
38
+ #
39
+ # The capturing can be disabled with the option :disable_capture.
40
+ # Output code in the block writes directly to the output buffer then.
41
+ # Rails handles this by replacing the output buffer for helpers.
42
+ options[:disable_capture] ? compile(content) : [:capture, unique_name, compile(content)]],
43
+
44
+ # Output the content.
45
+ [:escape, escape, [:dynamic, tmp]]]
46
+ end
47
+ end
48
+
49
+ # Handle text expression `[:slim, :text, content]`
50
+ #
51
+ # @param [Array] content Temple expression
52
+ # @return [Array] Compiled temple expression
53
+ def on_slim_text(content)
54
+ compile(content)
55
+ end
56
+ end
57
+ end
@@ -63,53 +63,78 @@ module Slim
63
63
  class EmbeddedEngine < Filter
64
64
  @engines = {}
65
65
 
66
- class << self
67
- attr_reader :engines
68
-
69
- # Register embedded engine
70
- #
71
- # @param [String] name Name of the engine
72
- # @param [Class] klass Engine class
73
- # @param option_filter List of options to pass to engine.
74
- # Last argument can be default option hash.
75
- def register(name, klass, *option_filter)
76
- local_options = Hash === option_filter.last ? option_filter.pop : nil
77
- @engines[name.to_s] = [klass, option_filter, local_options]
66
+ # Register embedded engine
67
+ #
68
+ # @param [String] name Name of the engine
69
+ # @param [Class] klass Engine class
70
+ # @param option_filter List of options to pass to engine.
71
+ # Last argument can be default option hash.
72
+ def self.register(name, klass, *option_filter)
73
+ name = name.to_sym
74
+ local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
75
+ define_options(name, *option_filter)
76
+ klass.define_options(name)
77
+ @engines[name.to_sym] = proc do |options|
78
+ klass.new({}.update(options).delete_if {|k,v| !option_filter.include?(k) && k != name }.update(local_options))
78
79
  end
79
80
  end
80
81
 
82
+ def self.create(name, options)
83
+ constructor = @engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found")
84
+ constructor.call(options)
85
+ end
86
+
87
+ define_options :enable_engines, :disable_engines
88
+
89
+ def initialize(opts = {})
90
+ super
91
+ @engines = {}
92
+ @enabled = normalize_engine_list(options[:enable_engines])
93
+ @disabled = normalize_engine_list(options[:disable_engines])
94
+ end
95
+
81
96
  def on_slim_embedded(name, body)
82
- name = name.to_s
83
- raise "Embedded engine #{name} is disabled" if (options[:enable_engines] && !options[:enable_engines].include?(name)) ||
84
- (options[:disable_engines] && options[:disable_engines].include?(name))
85
- engine, option_filter, local_options = self.class.engines[name] || raise("Embedded engine #{name} not found")
86
- filtered_options = Hash[*option_filter.select {|k| options.include?(k) }.map {|k| [k, options[k]] }.flatten]
87
- engine.new(Temple::ImmutableHash.new(local_options, filtered_options)).on_slim_embedded(name, body)
97
+ name = name.to_sym
98
+ raise(Temple::FilterError, "Embedded engine #{name} is disabled") unless enabled?(name)
99
+ @engines[name] ||= self.class.create(name, options)
100
+ @engines[name].on_slim_embedded(name, body)
101
+ end
102
+
103
+ def enabled?(name)
104
+ (!@enabled || @enabled.include?(name)) &&
105
+ (!@disabled || !@disabled.include?(name))
88
106
  end
89
107
 
90
108
  protected
91
109
 
92
- def collect_text(body)
93
- @text_collector ||= TextCollector.new
94
- @text_collector.call(body)
110
+ def normalize_engine_list(list)
111
+ raise(ArgumentError, "Option :enable_engines/:disable_engines must be String or Symbol list") unless !list || Array === list
112
+ list ? list.map {|s| s.to_sym } : list
95
113
  end
96
114
 
97
- # Basic tilt engine
98
- class TiltEngine < EmbeddedEngine
99
- def on_slim_embedded(engine, body)
100
- tilt_engine = Tilt[engine] || raise("Tilt engine #{engine} is not available.")
101
- tilt_options = options[engine.to_sym] || {}
102
- [:multi, tilt_render(tilt_engine, tilt_options, collect_text(body)), collect_newlines(body)]
103
- end
104
-
115
+ class Engine < Filter
105
116
  protected
106
117
 
118
+ def collect_text(body)
119
+ @text_collector ||= TextCollector.new
120
+ @text_collector.call(body)
121
+ end
122
+
107
123
  def collect_newlines(body)
108
124
  @newline_collector ||= NewlineCollector.new
109
125
  @newline_collector.call(body)
110
126
  end
111
127
  end
112
128
 
129
+ # Basic tilt engine
130
+ class TiltEngine < Engine
131
+ def on_slim_embedded(engine, body)
132
+ tilt_engine = Tilt[engine] || raise(Temple::FilterError, "Tilt engine #{engine} is not available.")
133
+ tilt_options = options[engine.to_sym] || {}
134
+ [:multi, tilt_render(tilt_engine, tilt_options, collect_text(body)), collect_newlines(body)]
135
+ end
136
+ end
137
+
113
138
  # Tilt-based static template (evaluated at compile-time)
114
139
  class StaticTiltEngine < TiltEngine
115
140
  protected
@@ -121,11 +146,14 @@ module Slim
121
146
 
122
147
  # Sass engine which supports :pretty option
123
148
  class SassEngine < TiltEngine
149
+ define_options :pretty
150
+
124
151
  protected
125
152
 
126
153
  def tilt_render(tilt_engine, tilt_options, text)
127
154
  text = tilt_engine.new(tilt_options.merge(
128
- :style => (options[:pretty] ? :expanded : :compressed), :cache => false)) { text }.render
155
+ :style => options[:pretty] ? :expanded : :compressed,
156
+ :cache => false)) { text }.render
129
157
  text.chomp!
130
158
  [:static, options[:pretty] ? "\n#{text}\n" : text]
131
159
  end
@@ -136,7 +164,7 @@ module Slim
136
164
  protected
137
165
 
138
166
  def tilt_render(tilt_engine, tilt_options, text)
139
- # WARNING: This is a bit of a hack. Tilt::Engine#precompiled is protected
167
+ # HACK: Tilt::Engine#precompiled is protected
140
168
  [:dynamic, tilt_engine.new(tilt_options) { text }.send(:precompiled, {}).first]
141
169
  end
142
170
  end
@@ -163,7 +191,7 @@ module Slim
163
191
  end
164
192
 
165
193
  # ERB engine (uses the Temple ERB implementation)
166
- class ERBEngine < EmbeddedEngine
194
+ class ERBEngine < Engine
167
195
  def on_slim_embedded(engine, body)
168
196
  [:multi, [:newline], erb_parser.call(collect_text(body))]
169
197
  end
@@ -177,15 +205,24 @@ module Slim
177
205
 
178
206
  # Tag wrapper engine
179
207
  # Generates a html tag and wraps another engine (specified via :engine option)
180
- class TagEngine < EmbeddedEngine
208
+ class TagEngine < Engine
209
+ disable_option_validator!
210
+
181
211
  def on_slim_embedded(engine, body)
182
- content = options[:engine] ? options[:engine].new(options).on_slim_embedded(engine, body) : body
183
- [:html, :tag, options[:tag], [:html, :attrs, *options[:attributes].map {|k, v| [:html, :attr, k, [:static, v]] }], content]
212
+ if options[:engine]
213
+ opts = {}.update(options)
214
+ opts.delete(:engine)
215
+ opts.delete(:tag)
216
+ opts.delete(:attributes)
217
+ @engine ||= options[:engine].new(opts)
218
+ body = @engine.on_slim_embedded(engine, body)
219
+ end
220
+ [:html, :tag, options[:tag], [:html, :attrs, *options[:attributes].map {|k, v| [:html, :attr, k, [:static, v]] }], body]
184
221
  end
185
222
  end
186
223
 
187
224
  # Embeds ruby code
188
- class RubyEngine < EmbeddedEngine
225
+ class RubyEngine < Engine
189
226
  def on_slim_embedded(engine, body)
190
227
  [:multi, [:newline], [:code, collect_text(body)]]
191
228
  end
@@ -200,11 +237,11 @@ module Slim
200
237
  register :mediawiki, InterpolateTiltEngine
201
238
 
202
239
  # These engines are executed at compile time
203
- register :coffee, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }, :engine => StaticTiltEngine
204
- register :less, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
205
- register :styl, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
206
- register :sass, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
207
- register :scss, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
240
+ register :coffee, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }, :engine => StaticTiltEngine
241
+ register :less, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
242
+ register :styl, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
243
+ register :sass, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
244
+ register :scss, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
208
245
 
209
246
  # These engines are precompiled, code is embedded
210
247
  register :erb, ERBEngine
@@ -212,8 +249,8 @@ module Slim
212
249
  register :builder, PrecompiledTiltEngine
213
250
 
214
251
  # Embedded javascript/css
215
- register :javascript, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }
216
- register :css, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }
252
+ register :javascript, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }
253
+ register :css, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }
217
254
 
218
255
  # Embedded ruby code
219
256
  register :ruby, RubyEngine