slim 1.3.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -8
  3. data/.travis.yml +8 -7
  4. data/CHANGES +35 -0
  5. data/Gemfile +9 -9
  6. data/README.md +94 -176
  7. data/Rakefile +7 -14
  8. data/benchmarks/run-benchmarks.rb +9 -37
  9. data/doc/logic_less.md +140 -0
  10. data/doc/translator.md +31 -0
  11. data/lib/slim.rb +3 -1
  12. data/lib/slim/code_attributes.rb +20 -20
  13. data/lib/slim/command.rb +16 -6
  14. data/lib/slim/do_inserter.rb +33 -0
  15. data/lib/slim/embedded.rb +0 -6
  16. data/lib/slim/end_inserter.rb +2 -2
  17. data/lib/slim/engine.rb +9 -23
  18. data/lib/slim/erb_converter.rb +14 -0
  19. data/lib/slim/filter.rb +2 -2
  20. data/lib/slim/logic_less/context.rb +8 -0
  21. data/lib/slim/logic_less/filter.rb +12 -11
  22. data/lib/slim/parser.rb +57 -113
  23. data/lib/slim/splat/builder.rb +79 -0
  24. data/lib/slim/splat/filter.rb +93 -0
  25. data/lib/slim/version.rb +1 -1
  26. data/slim.gemspec +1 -1
  27. data/test/core/helper.rb +1 -3
  28. data/test/core/test_code_blocks.rb +51 -0
  29. data/test/core/test_code_evaluation.rb +4 -12
  30. data/test/core/test_embedded_engines.rb +18 -44
  31. data/test/core/test_encoding.rb +8 -1
  32. data/test/core/test_erb_converter.rb +67 -0
  33. data/test/core/test_html_attributes.rb +19 -25
  34. data/test/core/test_html_escaping.rb +10 -2
  35. data/test/core/test_html_structure.rb +6 -6
  36. data/test/core/test_parser_errors.rb +1 -1
  37. data/test/core/test_ruby_errors.rb +2 -6
  38. data/test/core/test_thread_options.rb +4 -4
  39. data/test/core/test_unicode.rb +18 -0
  40. data/test/literate/TESTS.md +193 -34
  41. data/test/logic_less/test_logic_less.rb +17 -0
  42. data/test/rails/app/controllers/application_controller.rb +0 -1
  43. data/test/rails/app/controllers/entries_controller.rb +5 -0
  44. data/test/rails/app/controllers/slim_controller.rb +3 -0
  45. data/test/rails/app/models/entry.rb +16 -0
  46. data/test/rails/app/views/entries/edit.html.slim +3 -0
  47. data/test/rails/app/views/slim/form_for.html.slim +2 -0
  48. data/test/rails/app/views/slim/xml.slim +1 -0
  49. data/test/rails/config/application.rb +2 -2
  50. data/test/rails/config/environments/test.rb +1 -1
  51. data/test/rails/config/routes.rb +1 -1
  52. data/test/rails/test/helper.rb +0 -3
  53. data/test/rails/test/test_slim.rb +13 -8
  54. data/test/translator/test_translator.rb +13 -2
  55. metadata +17 -14
  56. data/lib/slim/splat_attributes.rb +0 -113
  57. data/test/rails/app/controllers/parents_controller.rb +0 -85
  58. data/test/rails/app/models/child.rb +0 -3
  59. data/test/rails/app/models/parent.rb +0 -4
  60. data/test/rails/app/views/parents/_form.html.slim +0 -8
  61. data/test/rails/app/views/parents/edit.html.slim +0 -2
  62. data/test/rails/app/views/parents/new.html.slim +0 -2
  63. data/test/rails/app/views/parents/show.html.slim +0 -5
  64. data/test/rails/config/database.yml +0 -4
  65. data/test/rails/db/migrate/20101220223037_parents_and_children.rb +0 -17
data/Rakefile CHANGED
@@ -51,8 +51,14 @@ namespace 'test' do
51
51
  require 'sinatra'
52
52
  spec = Gem::Specification.find_by_name('sinatra')
53
53
  Rake::TestTask.new('sinatra') do |t|
54
+ # FIXME: Rename deprecated attribute
55
+ file = "#{spec.gem_dir}/test/slim_test.rb"
56
+ code = File.read(file)
57
+ code.gsub!('attr_wrapper', 'attr_quote')
58
+ File.open(file, 'w') {|out| out.write(code) }
59
+
54
60
  # Run Slim integration test in Sinatra
55
- t.test_files = FileList["#{spec.gem_dir}/test/slim_test.rb"]
61
+ t.test_files = FileList[file]
56
62
  t.verbose = true
57
63
  end
58
64
  rescue LoadError
@@ -62,19 +68,6 @@ namespace 'test' do
62
68
  end
63
69
  end
64
70
 
65
- begin
66
- require 'rcov/rcovtask'
67
- Rcov::RcovTask.new do |t|
68
- t.libs << 'lib' << 'test/slim'
69
- t.test_files = FileList['test/slim/test_*.rb']
70
- t.verbose = true
71
- end
72
- rescue LoadError
73
- task :rcov do
74
- abort 'RCov is not available. In order to run rcov, you must: gem install rcov'
75
- end
76
- end
77
-
78
71
  begin
79
72
  require 'yard'
80
73
  YARD::Rake::YardocTask.new do |t|
@@ -22,7 +22,6 @@ class SlimBenchmarks
22
22
 
23
23
  init_compiled_benches
24
24
  init_tilt_benches
25
- init_cached_benches
26
25
  init_parsing_benches if slow
27
26
  end
28
27
 
@@ -76,41 +75,18 @@ class SlimBenchmarks
76
75
  bench('(2) haml ugly') { tilt_haml_ugly.render(context) }
77
76
  end
78
77
 
79
- def init_cached_benches
80
- context = Context.new
81
- context_binding = context.instance_eval { binding }
82
-
83
- erb = ERB.new(@erb_code)
84
- erubis = Erubis::Eruby.new(@erb_code)
85
- fast_erubis = Erubis::FastEruby.new(@erb_code)
86
- temple_erb = Temple::ERB::Template.new { @erb_code }
87
- haml_pretty = Haml::Engine.new(@haml_code, :format => :html5)
88
- haml_ugly = Haml::Engine.new(@haml_code, :format => :html5, :ugly => true)
89
- slim_pretty = Slim::Template.new(:pretty => true) { @slim_code }
90
- slim_ugly = Slim::Template.new { @slim_code }
91
-
92
- bench('(3) erb') { erb.result(context_binding) }
93
- bench('(3) erubis') { erubis.result(context_binding) }
94
- bench('(3) fast erubis') { fast_erubis.result(context_binding) }
95
- bench('(3) temple erb') { temple_erb.render(context) }
96
- bench('(3) slim pretty') { slim_pretty.render(context) }
97
- bench('(3) slim ugly') { slim_ugly.render(context) }
98
- bench('(3) haml pretty') { haml_pretty.render(context) }
99
- bench('(3) haml ugly') { haml_ugly.render(context) }
100
- end
101
-
102
78
  def init_parsing_benches
103
79
  context = Context.new
104
80
  context_binding = context.instance_eval { binding }
105
81
 
106
- bench('(4) erb') { ERB.new(@erb_code).result(context_binding) }
107
- bench('(4) erubis') { Erubis::Eruby.new(@erb_code).result(context_binding) }
108
- bench('(4) fast erubis') { Erubis::FastEruby.new(@erb_code).result(context_binding) }
109
- bench('(4) temple erb') { Temple::ERB::Template.new { @erb_code }.render(context) }
110
- bench('(4) slim pretty') { Slim::Template.new(:pretty => true) { @slim_code }.render(context) }
111
- bench('(4) slim ugly') { Slim::Template.new { @slim_code }.render(context) }
112
- bench('(4) haml pretty') { Haml::Engine.new(@haml_code, :format => :html5).render(context) }
113
- bench('(4) haml ugly') { Haml::Engine.new(@haml_code, :format => :html5, :ugly => true).render(context) }
82
+ bench('(3) erb') { ERB.new(@erb_code).result(context_binding) }
83
+ bench('(3) erubis') { Erubis::Eruby.new(@erb_code).result(context_binding) }
84
+ bench('(3) fast erubis') { Erubis::FastEruby.new(@erb_code).result(context_binding) }
85
+ bench('(3) temple erb') { Temple::ERB::Template.new { @erb_code }.render(context) }
86
+ bench('(3) slim pretty') { Slim::Template.new(:pretty => true) { @slim_code }.render(context) }
87
+ bench('(3) slim ugly') { Slim::Template.new { @slim_code }.render(context) }
88
+ bench('(3) haml pretty') { Haml::Engine.new(@haml_code, :format => :html5).render(context) }
89
+ bench('(3) haml ugly') { Haml::Engine.new(@haml_code, :format => :html5, :ugly => true).render(context) }
114
90
  end
115
91
 
116
92
  def run
@@ -133,11 +109,7 @@ class SlimBenchmarks
133
109
  Sinatra, Ramaze and Camping. (Rails still uses its own template
134
110
  compilation.)
135
111
 
136
- (3) Cached benchmark. Template is parsed before the benchmark.
137
- The ruby code generated by the template engine might be evaluated every time.
138
- This benchmark uses the standard API of the template engine.
139
-
140
- (4) Parsing benchmark. Template is parsed every time.
112
+ (3) Parsing benchmark. Template is parsed every time.
141
113
  This is not the recommended way to use the template engine
142
114
  and Slim is not optimized for it. Activate this benchmark with 'rake bench slow=1'.
143
115
 
data/doc/logic_less.md ADDED
@@ -0,0 +1,140 @@
1
+ # Logic less mode
2
+
3
+ Logic less mode is inspired by [Mustache](https://github.com/defunkt/mustache). Logic less mode uses a dictionary object
4
+ e.g. a recursive hash tree which contains the dynamic content.
5
+
6
+ ## Conditional
7
+
8
+ If the object is not false or empty?, the content will show
9
+
10
+ - article
11
+ h1 = title
12
+
13
+ ## Inverted conditional
14
+
15
+ If the object is false or empty?, the content will show
16
+
17
+ -! article
18
+ p Sorry, article not found
19
+
20
+ ## Iteration
21
+
22
+ If the object is an array, the section will iterate
23
+
24
+ - articles
25
+ tr: td = title
26
+
27
+ ## Lambdas
28
+
29
+ Like mustache, Slim supports lambdas.
30
+
31
+ = person
32
+ = name
33
+
34
+ The lambda method could be defined like this
35
+
36
+ def lambda_method
37
+ "<div class='person'>#{yield(:name => 'Andrew')}</div>"
38
+ end
39
+
40
+ You can optionally pass one or more hashes to `yield`. If you pass multiple hashes, the block will be iterated as described above.
41
+
42
+ ## Dictionary access
43
+
44
+ Example code:
45
+
46
+ - article
47
+ h1 = title
48
+
49
+ The dictionary object is accessed in the order given by the `:dictionary_access`. Default order:
50
+
51
+ 1. `:symbol` - If `article.respond_to?(:has_key?)` and `article.has_key?(:title)`, Slim will execute `article[:title]`
52
+ 2. `:string` - If `article.respond_to?(:has_key?)` and `article.has_key?('title')`, Slim will execute `article['title']`
53
+ 3. `:method` - If `article.respond_to?(:title)`, Slim will execute `article.send(:title)`
54
+ 4. `:instance_variable` - If `article.instance_variable_defined?(@title)`, Slim will execute `article.instance_variable_get @title`
55
+
56
+ If all the above fails, Slim will try to resolve the title reference in the same order against the parent object. In this example, the parent would be the dictionary object you are rendering the template against.
57
+
58
+ As you might have guessed, the article reference goes through the same steps against the dictionary. Instance variables are not allowed in the view code, but Slim will find and use them. Essentially, you're just using dropping the @ prefix in your template. Parameterized method calls are not allowed.
59
+
60
+
61
+ ## Strings
62
+
63
+ The `self` keyword will return the `.to_s` value for the element under consideration.
64
+
65
+ Given
66
+
67
+ {
68
+ :article => [
69
+ 'Article 1',
70
+ 'Article 2'
71
+ ]
72
+ }
73
+
74
+ And
75
+
76
+ - article
77
+ tr: td = self
78
+
79
+ This will yield
80
+
81
+ <tr>
82
+ <td>Article 1</td>
83
+ </>
84
+ <tr>
85
+ <td>Article 2</td>
86
+ </tr>
87
+
88
+
89
+ ## Logic less in Rails
90
+
91
+ Install:
92
+
93
+ $ gem install slim
94
+
95
+ Require:
96
+
97
+ gem 'slim', :require => 'slim/logic_less'
98
+
99
+ You might want to activate logic less mode only for a few actions, you should disable logic-less mode globally at first in the configuration
100
+
101
+ Slim::Engine.set_default_options :logic_less => false
102
+
103
+ and activate logic less mode per render call in your action
104
+
105
+ class Controller
106
+ def action
107
+ Slim::Engine.with_options(:logic_less => true) do
108
+ render
109
+ end
110
+ end
111
+ end
112
+
113
+ ## Logic less in Sinatra
114
+
115
+ Sinata has built-in support for Slim. All you have to do is require the logic less Slim plugin. This can be done in your config.ru:
116
+
117
+ require 'slim/logic_less'
118
+
119
+ You are then ready to rock!
120
+
121
+ You might want to activate logic less mode only for a few actions, you should disable logic-less mode globally at first in the configuration
122
+
123
+ Slim::Engine.set_default_options :logic_less => false
124
+
125
+ and activate logic less mode per render call in your application
126
+
127
+ get '/page'
128
+ slim :page, :logic_less => true
129
+ end
130
+
131
+ ## Options
132
+
133
+ <table>
134
+ <thead style="font-weight:bold"><tr><td>Type</td><td>Name</td><td>Default</td><td>Purpose</td></tr></thead>
135
+ <tbody>
136
+ <tr><td>Boolean</td><td>:logic_less</td><td>true</td><td>Enable logic less mode (Enabled if 'slim/logic_less' is required)</td></tr>
137
+ <tr><td>String</td><td>:dictionary</td><td>"self"</td><td>Dictionary where variables are looked up</td></tr>
138
+ <tr><td>Symbol/Array&lt;Symbol&gt;</td><td>:dictionary_access</td><td>[:symbol, :string, :method, :instance_variable]</td><td>Dictionary access order (:symbol, :string, :method, :instance_variable)</td></tr>
139
+ </tbody>
140
+ </table>
data/doc/translator.md ADDED
@@ -0,0 +1,31 @@
1
+ # Translator/I18n
2
+
3
+ The translator plugin provides automatic translation of the templates using Gettext, Fast-Gettext or Rails I18n. Static text
4
+ in the template is replaced by the translated version.
5
+
6
+ Example:
7
+
8
+ h1 Welcome to #{url}!
9
+
10
+ Gettext translates the string from english to german where interpolations are replaced by %1, %2, ...
11
+
12
+ "Welcome to %1!" -> "Willkommen auf %1!"
13
+
14
+ and renders as
15
+
16
+ <h1>Willkommen auf slim-lang.com!</h1>
17
+
18
+ Enable the translator plugin with
19
+
20
+ require 'slim/translator'
21
+
22
+ # Options
23
+
24
+ <table>
25
+ <thead style="font-weight:bold"><tr><td>Type</td><td>Name</td><td>Default</td><td>Purpose</td></tr></thead>
26
+ <tbody>
27
+ <tr><td>Boolean</td><td>:tr</td><td>true</td><td>Enable translator (Enabled if 'slim/translator' is required)</td></tr>
28
+ <tr><td>Symbol</td><td>:tr_mode</td><td>:dynamic</td><td>When to translate: :static = at compile time, :dynamic = at runtime</td></tr>
29
+ <tr><td>String</td><td>:tr_fn</td><td>Depending on installed translation library</td><td>Translation function, could be '_' for gettext</td></tr>
30
+ </tbody>
31
+ </table>
data/lib/slim.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  require 'temple'
2
2
  require 'slim/parser'
3
3
  require 'slim/filter'
4
+ require 'slim/do_inserter'
4
5
  require 'slim/end_inserter'
5
6
  require 'slim/embedded'
6
7
  require 'slim/interpolation'
7
8
  require 'slim/controls'
8
- require 'slim/splat_attributes'
9
+ require 'slim/splat/filter'
10
+ require 'slim/splat/builder'
9
11
  require 'slim/code_attributes'
10
12
  require 'slim/engine'
11
13
  require 'slim/template'
@@ -11,34 +11,34 @@ module Slim
11
11
  [:multi, *attrs.map {|a| compile(a) }]
12
12
  end
13
13
 
14
- # Handle attribute expression `[:slim, :attr, escape, code]`
14
+ # Handle attribute expression `[:html, :attr, escape, code]`
15
15
  #
16
16
  # @param [String] name Attribute name
17
17
  # @param [Array] value Value expression
18
18
  # @return [Array] Compiled temple expression
19
19
  def on_html_attr(name, value)
20
- unless value[0] == :slim && value[1] == :attrvalue && !options[:merge_attrs][name]
21
- # We perform merging on the attribute
20
+ if value[0] == :slim && value[1] == :attrvalue && !options[:merge_attrs][name]
21
+ # We handle the attribute as a boolean attribute
22
+ escape, code = value[2], value[3]
23
+ case code
24
+ when 'true'
25
+ [:html, :attr, name, [:static, name]]
26
+ when 'false', 'nil'
27
+ [:multi]
28
+ else
29
+ tmp = unique_name
30
+ [:multi,
31
+ [:code, "#{tmp} = #{code}"],
32
+ [:case, tmp,
33
+ ['true', [:html, :attr, name, [:static, name]]],
34
+ ['false, nil', [:multi]],
35
+ [:else, [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]]]
36
+ end
37
+ else
38
+ # Attribute with merging
22
39
  @attr = name
23
40
  return super
24
41
  end
25
-
26
- # We handle the attribute as a boolean attribute
27
- escape, code = value[2], value[3]
28
- case code
29
- when 'true'
30
- [:html, :attr, name, [:static, name]]
31
- when 'false', 'nil'
32
- [:multi]
33
- else
34
- tmp = unique_name
35
- [:multi,
36
- [:code, "#{tmp} = #{code}"],
37
- [:case, tmp,
38
- ['true', [:html, :attr, name, [:static, name]]],
39
- ['false, nil', [:multi]],
40
- [:else, [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]]]
41
- end
42
42
  end
43
43
 
44
44
  # Handle attribute expression `[:slim, :attrvalue, escape, code]`
data/lib/slim/command.rb CHANGED
@@ -42,6 +42,10 @@ module Slim
42
42
  @options[:compile] = true
43
43
  end
44
44
 
45
+ opts.on('-e', '--erb', 'Convert to ERB') do
46
+ @options[:erb] = true
47
+ end
48
+
45
49
  opts.on('-r', '--rails', 'Generate rails compatible code (Implies --compile)') do
46
50
  Engine.set_default_options :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer
47
51
  @options[:compile] = true
@@ -70,7 +74,7 @@ module Slim
70
74
  end
71
75
 
72
76
  opts.on_tail('-v', '--version', 'Print version') do
73
- puts "Slim #{Slim::VERSION}"
77
+ puts "Slim #{VERSION}"
74
78
  exit
75
79
  end
76
80
  end
@@ -94,11 +98,17 @@ module Slim
94
98
  @options[:output] = file ? File.open(file, 'w') : $stdout
95
99
  end
96
100
 
97
- if @options[:compile]
98
- @options[:output].puts(Slim::Engine.new(:file => @options[:file]).call(@options[:input].read))
99
- else
100
- @options[:output].puts(Slim::Template.new(@options[:file]) { @options[:input].read }.render)
101
- end
101
+ result =
102
+ if @options[:erb]
103
+ require 'slim/erb_converter'
104
+ ERBConverter.new(:file => @options[:file]).call(@options[:input].read)
105
+ elsif @options[:compile]
106
+ Engine.new(:file => @options[:file]).call(@options[:input].read)
107
+ else
108
+ Template.new(@options[:file]) { @options[:input].read }.render
109
+ end
110
+
111
+ @options[:output].puts(result)
102
112
  end
103
113
  end
104
114
  end
@@ -0,0 +1,33 @@
1
+ module Slim
2
+ # In Slim you don't need the do keyword sometimes. This
3
+ # filter adds the missing keyword.
4
+ #
5
+ # - 10.times
6
+ # | Hello
7
+ #
8
+ # @api private
9
+ class DoInserter < Filter
10
+ BLOCK_REGEX = /(\A(if|unless|else|elsif|when|begin|rescue|ensure)\b)|do\s*(\|[^\|]*\|\s*)?\Z/
11
+
12
+ # Handle control expression `[:slim, :control, code, content]`
13
+ #
14
+ # @param [String] code Ruby code
15
+ # @param [Array] content Temple expression
16
+ # @return [Array] Compiled temple expression
17
+ def on_slim_control(code, content)
18
+ code = code + ' do' unless code =~ BLOCK_REGEX || empty_exp?(content)
19
+ [:slim, :control, code, compile(content)]
20
+ end
21
+
22
+ # Handle output expression `[:slim, :output, escape, code, content]`
23
+ #
24
+ # @param [Boolean] escape Escape html
25
+ # @param [String] code Ruby code
26
+ # @param [Array] content Temple expression
27
+ # @return [Array] Compiled temple expression
28
+ def on_slim_output(escape, code, content)
29
+ code = code + ' do' unless code =~ BLOCK_REGEX || empty_exp?(content)
30
+ [:slim, :output, escape, code, compile(content)]
31
+ end
32
+ end
33
+ end
data/lib/slim/embedded.rb CHANGED
@@ -268,10 +268,4 @@ module Slim
268
268
  # Embedded ruby code
269
269
  register :ruby, RubyEngine
270
270
  end
271
-
272
- def self.const_missing(name)
273
- super unless name == :EmbeddedEngine
274
- warn 'Slim::EmbeddedEngine is deprecated, it is called Slim::Embedded in Slim 2.0'
275
- Embedded
276
- end
277
271
  end
@@ -10,7 +10,7 @@ module Slim
10
10
  #
11
11
  # @api private
12
12
  class EndInserter < Filter
13
- ELSE_REGEX = /\A(else|elsif|when|rescue|ensure)\b/
13
+ BLOCK_REGEX = /\A(else|elsif|when|rescue|ensure)\b/
14
14
  END_REGEX = /\Aend\b/
15
15
 
16
16
  # Handle multi expression `[:multi, *exps]`
@@ -28,7 +28,7 @@ module Slim
28
28
 
29
29
  # Two control code in a row. If this one is *not*
30
30
  # an else block, we should close the previous one.
31
- append_end(result) if prev_indent && exp[2] !~ ELSE_REGEX
31
+ append_end(result) if prev_indent && exp[2] !~ BLOCK_REGEX
32
32
 
33
33
  # Indent if the control code contains something.
34
34
  prev_indent = !empty_exp?(exp[3])