slim 1.2.2 → 1.3.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.
@@ -2,12 +2,9 @@ module Slim
2
2
  # Slim engine which transforms slim code to executable ruby code
3
3
  # @api public
4
4
  class Engine < Temple::Engine
5
- # Allow users to set default options, particularly useful in Rails' environment files.
6
- # For instance, in config/environments/development.rb you probably want:
7
- # # Indent html for pretty debugging
8
- # Slim::Engine.set_default_options :pretty => true
9
- #
10
- # This overwrites some temple default options.
5
+ # This overwrites some Temple default options or sets default options for Slim specific filters.
6
+ # It is recommended to set the default settings only once in the code and avoid duplication. Only use
7
+ # `set_default_options` when you have to override some default settings.
11
8
  set_default_options :pretty => false,
12
9
  :sort_attrs => true,
13
10
  :attr_wrapper => '"',
@@ -15,54 +12,10 @@ module Slim
15
12
  :remove_empty_attrs => true,
16
13
  :generator => Temple::Generators::ArrayBuffer,
17
14
  :default_tag => 'div'
18
- #
19
- # Document all supported options with purpose, type etc.
20
- #
21
- # Type | Name | Default value | Purpose
22
- # --------------------------------------------------------------------------------------------------------------------------------------------
23
- # String | :file | nil | Name of parsed file, set automatically by Slim::Template
24
- # Integer | :tabsize | 4 | Number of whitespaces per tab (used by the parser)
25
- # String | :encoding | "utf-8" | Set encoding of template
26
- # String | :default_tag | "div" | Default tag to be used if tag name is omitted
27
- # Hash | :shortcut | {'.' => 'class', ...} | Attribute shortcuts
28
- # String list | :enable_engines | All enabled | List of enabled embedded engines (whitelist)
29
- # String list | :disable_engines | None disabled | List of disabled embedded engines (blacklist)
30
- # Boolean | :sections | false | Enable sections mode (logic-less)
31
- # String | :dictionary | "self" | Name of dictionary variable in sections mode
32
- # Symbol | :dictionary_access | :wrapped | Access mode of dictionary variable (:wrapped, :symbol, :string)
33
- # Boolean | :disable_capture | false (true in Rails) | Disable capturing in blocks (blocks write to the default buffer
34
- # Boolean | :disable_escape | false | Disable automatic escaping of strings
35
- # Boolean | :use_html_safe | false (true in Rails) | Use String#html_safe? from ActiveSupport (Works together with :disable_escape)
36
- # Symbol | :format | :xhtml | HTML output format
37
- # String | :attr_wrapper | '"' | Character to wrap attributes in html (can be ' or ")
38
- # Hash | :attr_delimiter | {'class' => ' '} | Joining character used if multiple html attributes are supplied (e.g. id1_id2)
39
- # Boolean | :sort_attrs | true | Sort attributes by name
40
- # Boolean | :remove_empty_attrs| true | Remove attributes with empty value
41
- # Boolean | :pretty | false | Pretty html indenting (This is slower!)
42
- # String | :indent | ' ' | Indentation string
43
- # Boolean | :streaming | false (true in Rails > 3.1) | Enable output streaming
44
- # Class | :generator | ArrayBuffer/RailsOutputBuffer | Temple code generator (default generator generates array buffer)
45
- #
46
- # It is also possible to set all options supported by the generator (option :generator). The standard generators
47
- # support the options :buffer and :capture_generator.
48
- #
49
- # Options can be set at multiple positions. Slim/Temple uses a inheritance mechanism to allow
50
- # subclasses to overwrite options of the superclass. The option priorities are as follows:
51
- #
52
- # Custom (Options passed by the user) > Slim::Template > Slim::Engine > Parser/Filter/Generator (e.g Slim::Parser, Slim::Compiler)
53
- #
54
- # It is also possible to set options for superclasses like Temple::Engine. But this will affect all temple template engines then.
55
- #
56
- # Slim::Engine > Temple::Engine
57
- # Slim::Compiler > Temple::Filter
58
- #
59
- # It is recommended to set the default settings only once in the code and avoid duplication. Only use
60
- # `set_default_options` when you have to override some default settings.
61
- #
15
+
62
16
  use Slim::Parser, :file, :tabsize, :encoding, :shortcut, :default_tag
63
17
  use Slim::EmbeddedEngine, :enable_engines, :disable_engines, :pretty
64
18
  use Slim::Interpolation
65
- use Slim::Sections, :sections, :dictionary, :dictionary_access
66
19
  use Slim::EndInserter
67
20
  use Slim::Compiler, :disable_capture, :attr_delimiter, :attr_wrapper, :sort_attrs, :remove_empty_attrs, :default_tag
68
21
  html :AttributeMerger, :attr_delimiter
@@ -7,6 +7,11 @@ module Slim
7
7
  #
8
8
  # @api private
9
9
  class Filter < Temple::HTML::Filter
10
+ # Pass-through handler
11
+ def on_slim_text(content)
12
+ [:slim, :text, compile(content)]
13
+ end
14
+
10
15
  # Pass-through handler
11
16
  def on_slim_embedded(type, content)
12
17
  [:slim, :embedded, code, compile(content)]
@@ -17,11 +22,6 @@ module Slim
17
22
  [:slim, :control, code, compile(content)]
18
23
  end
19
24
 
20
- # Pass-through handler
21
- def on_slim_condcomment(condition, content)
22
- [:slim, :condcomment, condition, compile(content)]
23
- end
24
-
25
25
  # Pass-through handler
26
26
  def on_slim_output(code, escape, content)
27
27
  [:slim, :output, code, escape, compile(content)]
@@ -6,10 +6,10 @@ module Slim
6
6
 
7
7
  Expression <<
8
8
  [:slim, :control, String, Expression] |
9
- [:slim, :condcomment, String, Expression] |
10
9
  [:slim, :output, Bool, String, Expression] |
11
10
  [:slim, :interpolate, String] |
12
11
  [:slim, :embedded, String, Expression] |
12
+ [:slim, :text, Expression] |
13
13
  [:slim, :tag, String, SlimAttrs, 'Expression?']
14
14
 
15
15
  SlimAttrs <<
@@ -16,9 +16,7 @@ module Slim
16
16
  case string
17
17
  when /\A\\#\{/
18
18
  # Escaped interpolation
19
- # Use [:slim, :output] because this is used by InterpolateTiltEngine
20
- # to filter out protected strings (Issue #141).
21
- block << [:slim, :output, false, '\'#{\'', [:multi]]
19
+ block << [:static, '#{']
22
20
  string = $'
23
21
  when /\A#\{/
24
22
  # Interpolation
@@ -0,0 +1,6 @@
1
+ require 'slim'
2
+ require 'slim/logic_less/filter'
3
+ require 'slim/logic_less/wrapper'
4
+
5
+ # Insert plugin filter into Slim engine chain
6
+ Slim::Engine.after(Slim::Interpolation, Slim::LogicLess, :logic_less, :dictionary, :dictionary_access)
@@ -1,10 +1,10 @@
1
1
  module Slim
2
2
  # Handle logic-less mode
3
- # This filter can be activated with the option "sections"
3
+ # This filter can be activated with the option "logic_less"
4
4
  # @api private
5
- class Sections < Filter
6
- set_default_options :dictionary => 'self',
7
- :sections => false,
5
+ class LogicLess < Filter
6
+ set_default_options :logic_less => true,
7
+ :dictionary => 'self',
8
8
  :dictionary_access => :wrapped # :symbol, :string, :wrapped
9
9
 
10
10
  def initialize(opts = {})
@@ -15,12 +15,12 @@ module Slim
15
15
  end
16
16
 
17
17
  def call(exp)
18
- if options[:sections]
19
- # Store the dictionary in the _slimdict variable
18
+ if options[:logic_less]
19
+ @dict = unique_name
20
20
  dictionary = options[:dictionary]
21
- dictionary = "Slim::Wrapper.new(#{dictionary})" if options[:dictionary_access] == :wrapped
21
+ dictionary = "::Slim::LogicLess::Wrapper.new(#{dictionary})" if options[:dictionary_access] == :wrapped
22
22
  [:multi,
23
- [:code, "_slimdict = #{dictionary}"],
23
+ [:code, "#{@dict} = #{dictionary}"],
24
24
  super]
25
25
  else
26
26
  exp
@@ -37,7 +37,7 @@ module Slim
37
37
  end
38
38
 
39
39
  def on_slim_output(escape, name, content)
40
- raise 'Output statements with content are forbidden in sections mode' if !empty_exp?(content)
40
+ raise 'Output statements with content are forbidden in logic less mode' if !empty_exp?(content)
41
41
  [:slim, :output, escape, access(name), content]
42
42
  end
43
43
 
@@ -50,11 +50,11 @@ module Slim
50
50
  end
51
51
 
52
52
  def on_dynamic(code)
53
- raise 'Embedded code is forbidden in sections mode'
53
+ raise 'Embedded code is forbidden in logic less mode'
54
54
  end
55
55
 
56
56
  def on_code(code)
57
- raise 'Embedded code is forbidden in sections mode'
57
+ raise 'Embedded code is forbidden in logic less mode'
58
58
  end
59
59
 
60
60
  protected
@@ -77,9 +77,9 @@ module Slim
77
77
  [:multi,
78
78
  # Wrap map in array because maps implement each
79
79
  [:code, "#{tmp1} = [#{tmp1}] if #{tmp1}.respond_to?(:has_key?) || !#{tmp1}.respond_to?(:map)"],
80
- [:code, "#{tmp2} = _slimdict"],
81
- [:block, "#{tmp1}.each do |_slimdict|", content],
82
- [:code, "_slimdict = #{tmp2}"]]]]
80
+ [:code, "#{tmp2} = #{@dict}"],
81
+ [:block, "#{tmp1}.each do |#{@dict}|", content],
82
+ [:code, "#{@dict} = #{tmp2}"]]]]
83
83
  end
84
84
 
85
85
  private
@@ -88,9 +88,9 @@ module Slim
88
88
  return name if name == 'yield'
89
89
  case options[:dictionary_access]
90
90
  when :string
91
- "_slimdict[#{name.to_s.inspect}]"
91
+ "#{@dict}[#{name.to_s.inspect}]"
92
92
  else
93
- "_slimdict[#{name.to_sym.inspect}]"
93
+ "#{@dict}[#{name.to_sym.inspect}]"
94
94
  end
95
95
  end
96
96
  end
@@ -0,0 +1,64 @@
1
+ module Slim
2
+ class LogicLess
3
+ # For logic-less mode, objects can be encased in the Wrapper class.
4
+ # @api private
5
+ class Wrapper
6
+ attr_reader :value, :parent
7
+
8
+ def initialize(value, parent = nil)
9
+ @value, @parent = value, parent
10
+ end
11
+
12
+ # To find the reference, first check for standard method
13
+ # access by using respond_to?.
14
+ #
15
+ # If not found, check to see if the value is a hash and if the
16
+ # the name is a key on the hash.
17
+ #
18
+ # Not a hash, or not a key on the hash, then check to see if there
19
+ # is an instance variable with the name.
20
+ #
21
+ # If the instance variable doesn't exist and there is a parent object,
22
+ # go through the same steps on the parent object. This is useful when
23
+ # you are iterating over objects.
24
+ def [](name)
25
+ return wrap(value.send(name)) if value.respond_to?(name)
26
+ if value.respond_to?(:has_key?)
27
+ return wrap(value[name.to_sym]) if value.has_key?(name.to_sym)
28
+ return wrap(value[name.to_s]) if value.has_key?(name.to_s)
29
+ end
30
+ begin
31
+ var_name = "@#{name}"
32
+ return wrap(value.instance_variable_get(var_name)) if value.instance_variable_defined?(var_name)
33
+ rescue NameError
34
+ # Do nothing
35
+ end
36
+ parent[name] if parent
37
+ end
38
+
39
+ # Empty objects must appear empty for inverted sections
40
+ def empty?
41
+ value.respond_to?(:empty) && value.empty?
42
+ end
43
+
44
+ # Used for output
45
+ def to_s
46
+ value.to_s
47
+ end
48
+
49
+ private
50
+
51
+ def wrap(response)
52
+ # Primitives are not wrapped
53
+ if [String, Numeric, TrueClass, FalseClass, NilClass].any? {|primitive| primitive === response }
54
+ response
55
+ # Enumerables are mapped with wrapped values (except Hash-like objects)
56
+ elsif !response.respond_to?(:has_key?) && response.respond_to?(:map)
57
+ response.map {|v| wrap(v) }
58
+ else
59
+ Wrapper.new(response, self)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -64,7 +64,7 @@ module Slim
64
64
  end
65
65
 
66
66
  result = [:multi]
67
- reset(str.split($/), [result])
67
+ reset(str.split(/\r?\n/), [result])
68
68
 
69
69
  parse_line while next_line
70
70
 
@@ -180,11 +180,11 @@ module Slim
180
180
  # Found a comment block.
181
181
  if @line =~ %r{\A/!( ?)(.*)\Z}
182
182
  # HTML comment
183
- @stacks.last << [:html, :comment, parse_text_block($2, @indents.last + $1.size + 2)]
183
+ @stacks.last << [:html, :comment, [:slim, :text, parse_text_block($2, @indents.last + $1.size + 2)]]
184
184
  elsif @line =~ %r{\A/\[\s*(.*?)\s*\]\s*\Z}
185
185
  # HTML conditional comment
186
186
  block = [:multi]
187
- @stacks.last << [:slim, :condcomment, $1, block]
187
+ @stacks.last << [:html, :condcomment, $1, block]
188
188
  @stacks << block
189
189
  else
190
190
  # Slim comment
@@ -193,13 +193,13 @@ module Slim
193
193
  when /\A([\|'])( ?)(.*)\Z/
194
194
  # Found a text block.
195
195
  trailing_ws = $1 == "'"
196
- @stacks.last << parse_text_block($3, @indents.last + $2.size + 1)
196
+ @stacks.last << [:slim, :text, parse_text_block($3, @indents.last + $2.size + 1)]
197
197
  @stacks.last << [:static, ' '] if trailing_ws
198
198
  when /\A-/
199
199
  # Found a code block.
200
200
  # We expect the line to be broken or the next line to be indented.
201
- block = [:multi]
202
201
  @line.slice!(0)
202
+ block = [:multi]
203
203
  @stacks.last << [:slim, :control, parse_broken_line, block]
204
204
  @stacks << block
205
205
  when /\A=/
@@ -306,10 +306,9 @@ module Slim
306
306
  @stacks.delete_at(i)
307
307
  when /\A\s*=(=?)('?)/
308
308
  # Handle output code
309
- block = [:multi]
310
309
  @line = $'
311
- content = [:slim, :output, $1 != '=', parse_broken_line, block]
312
- tag << content
310
+ block = [:multi]
311
+ tag << [:slim, :output, $1 != '=', parse_broken_line, block]
313
312
  @stacks.last << [:static, ' '] unless $2.empty?
314
313
  @stacks << block
315
314
  when /\A\s*\//
@@ -321,17 +320,16 @@ module Slim
321
320
  @stacks << content
322
321
  when /\A( ?)(.*)\Z/
323
322
  # Text content
324
- tag << parse_text_block($2, @orig_line.size - @line.size + $1.size, true)
323
+ tag << [:slim, :text, parse_text_block($2, @orig_line.size - @line.size + $1.size, true)]
325
324
  end
326
325
  end
327
326
 
328
327
  def parse_attributes
329
328
  attributes = [:slim, :attrs]
330
- attribute = nil
331
329
 
332
330
  # Find any shortcut attributes
333
331
  while @line =~ @shortcut_regex
334
- # The class/id attribute is :static instead of :slim :text,
332
+ # The class/id attribute is :static instead of :slim :interpolate,
335
333
  # because we don't want text interpolation in .class or #id shortcut
336
334
  attributes << [:html, :attr, @shortcut[$1][1], [:static, $2]]
337
335
  @line = $'
@@ -0,0 +1,117 @@
1
+ require 'slim'
2
+
3
+ module Slim
4
+ class Translator < Filter
5
+ set_default_options :tr_mode => :dynamic,
6
+ :tr_fn => '_'
7
+
8
+ if Object.const_defined?(:I18n)
9
+ set_default_options :tr_fn => '::Slim::Translator.i18n_text',
10
+ :tr => true
11
+ elsif Object.const_defined?(:GetText)
12
+ set_default_options :tr_fn => '::GetText._',
13
+ :tr => true
14
+ elsif Object.const_defined?(:FastGettext)
15
+ set_default_options :tr_fn => '::FastGettext::Translation._',
16
+ :tr => true
17
+ end
18
+
19
+ def self.i18n_text(text)
20
+ I18n.t!(text)
21
+ rescue I18n::MissingTranslationData
22
+ text
23
+ end
24
+
25
+ def self.i18n_key(text)
26
+ key = text.parameterize.underscore
27
+ I18n.t!(key)
28
+ rescue I18n::MissingTranslationData
29
+ text
30
+ end
31
+
32
+ def call(exp)
33
+ if options[:tr]
34
+ super
35
+ else
36
+ exp
37
+ end
38
+ end
39
+
40
+ def initialize(opts)
41
+ super
42
+ case options[:tr_mode]
43
+ when :static
44
+ @translator = StaticTranslator.new(options)
45
+ when :dynamic
46
+ @translator = DynamicTranslator.new(options)
47
+ else
48
+ raise "Invalid translator mode #{options[:tr_mode].inspect}"
49
+ end
50
+ end
51
+
52
+ def on_slim_text(exp)
53
+ [:slim, :text, @translator.call(exp)]
54
+ end
55
+
56
+ private
57
+
58
+ class StaticTranslator < Filter
59
+ def initialize(opts)
60
+ super
61
+ @translate = eval("proc {|string| #{options[:tr_fn]}(string) }")
62
+ end
63
+
64
+ def call(exp)
65
+ @text, @captures = '', []
66
+ result = compile(exp)
67
+
68
+ text = @translate.call(@text)
69
+ while text =~ /%(\d+)/
70
+ result << [:static, $`] << @captures[$1.to_i - 1]
71
+ text = $'
72
+ end
73
+ result << [:static, text]
74
+ end
75
+
76
+ def on_static(text)
77
+ @text << text
78
+ [:multi]
79
+ end
80
+
81
+ def on_slim_output(escape, code, content)
82
+ @captures << [:slim, :output, escape, code, content]
83
+ @text << "%#{@captures.size}"
84
+ [:multi]
85
+ end
86
+ end
87
+
88
+ class DynamicTranslator < Filter
89
+ def call(exp)
90
+ @captures_count, @captures_var, @text = 0, unique_name, ''
91
+
92
+ result = compile(exp)
93
+
94
+ if @captures_count > 0
95
+ result.insert(1, [:code, "#{@captures_var}=[]"])
96
+ result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect}).gsub(/%(\\d+)/) { #{@captures_var}[$1.to_i-1] }", [:multi]]
97
+ else
98
+ result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect})", [:multi]]
99
+ end
100
+ end
101
+
102
+ def on_static(text)
103
+ @text << text
104
+ [:multi]
105
+ end
106
+
107
+ def on_slim_output(escape, code, content)
108
+ @captures_count += 1
109
+ @text << "%#{@captures_count}"
110
+ [:capture, "#{@captures_var}[#{@captures_count-1}]", [:slim, :output, escape, code, content]]
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ # Insert plugin filter into Slim engine chain
117
+ Slim::Engine.before(Slim::EndInserter, Slim::Translator, :tr, :tr_fn, :tr_mode)