slim 2.0.3 → 2.1.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -8
  3. data/.yardopts +1 -1
  4. data/CHANGES +23 -0
  5. data/Gemfile +2 -1
  6. data/LICENSE +1 -1
  7. data/README.jp.md +497 -268
  8. data/README.md +505 -278
  9. data/Rakefile +14 -2
  10. data/benchmarks/view.haml +2 -2
  11. data/doc/include.md +20 -0
  12. data/doc/jp/include.md +20 -0
  13. data/doc/jp/logic_less.md +137 -0
  14. data/doc/jp/smart.md +102 -0
  15. data/doc/jp/translator.md +28 -0
  16. data/doc/smart.md +102 -0
  17. data/lib/slim/command.rb +8 -2
  18. data/lib/slim/controls.rb +4 -3
  19. data/lib/slim/do_inserter.rb +1 -1
  20. data/lib/slim/embedded.rb +26 -22
  21. data/lib/slim/end_inserter.rb +1 -1
  22. data/lib/slim/engine.rb +3 -2
  23. data/lib/slim/filter.rb +2 -2
  24. data/lib/slim/grammar.rb +3 -1
  25. data/lib/slim/include.rb +54 -0
  26. data/lib/slim/interpolation.rb +1 -1
  27. data/lib/slim/parser.rb +61 -46
  28. data/lib/slim/smart.rb +8 -0
  29. data/lib/slim/smart/escaper.rb +42 -0
  30. data/lib/slim/smart/filter.rb +96 -0
  31. data/lib/slim/smart/parser.rb +33 -0
  32. data/lib/slim/splat/builder.rb +3 -3
  33. data/lib/slim/splat/filter.rb +2 -2
  34. data/lib/slim/translator.rb +2 -2
  35. data/lib/slim/version.rb +1 -1
  36. data/slim.gemspec +1 -1
  37. data/test/core/helper.rb +5 -3
  38. data/test/core/test_code_blocks.rb +11 -0
  39. data/test/core/test_code_escaping.rb +14 -4
  40. data/test/core/test_embedded_engines.rb +26 -0
  41. data/test/core/test_html_structure.rb +39 -0
  42. data/test/core/test_parser_errors.rb +8 -25
  43. data/test/core/test_pretty.rb +15 -0
  44. data/test/core/test_ruby_errors.rb +0 -10
  45. data/test/include/files/recursive.slim +1 -0
  46. data/test/include/files/slimfile.slim +3 -0
  47. data/test/include/files/subdir/test.slim +1 -0
  48. data/test/include/files/textfile +1 -0
  49. data/test/include/test_include.rb +23 -0
  50. data/test/literate/TESTS.md +17 -2
  51. data/test/rails/test/test_slim.rb +1 -1
  52. data/test/smart/test_smart_text.rb +300 -0
  53. data/test/translator/test_translator.rb +7 -6
  54. metadata +22 -4
@@ -39,16 +39,22 @@ module Slim
39
39
  @options[:erb] = true
40
40
  end
41
41
 
42
- opts.on('-r', '--rails', 'Generate rails compatible code (Implies --compile)') do
42
+ opts.on('--rails', 'Generate rails compatible code (Implies --compile)') do
43
43
  Engine.set_default_options :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer
44
44
  @options[:compile] = true
45
45
  end
46
46
 
47
+ opts.on('-r library', "Load library or plugin with -r slim/plugin") do |lib|
48
+ require lib
49
+ end
50
+
47
51
  opts.on('-t', '--translator', 'Enable translator plugin') do
52
+ puts "Deprecated option: Use -r slim/translator"
48
53
  require 'slim/translator'
49
54
  end
50
55
 
51
56
  opts.on('-l', '--logic-less', 'Enable logic less plugin') do
57
+ puts "Deprecated option: Use -r slim/logic_less"
52
58
  require 'slim/logic_less'
53
59
  end
54
60
 
@@ -56,7 +62,7 @@ module Slim
56
62
  Engine.set_default_options :pretty => true
57
63
  end
58
64
 
59
- opts.on('-o', '--option [NAME=CODE]', String, 'Set slim option') do |str|
65
+ opts.on('-o', '--option name=code', String, 'Set slim option') do |str|
60
66
  parts = str.split('=', 2)
61
67
  Engine.default_options[parts.first.gsub(/\A:/, '').to_sym] = eval(parts.last)
62
68
  end
@@ -3,7 +3,7 @@ module Slim
3
3
  class Controls < Filter
4
4
  define_options :disable_capture
5
5
 
6
- IF_RE = /\A(if|unless)\b|do\s*(\|[^\|]*\|)?\s*$/
6
+ IF_RE = /\A(if|unless)\b|\bdo\s*(\|[^\|]*\|)?\s*$/
7
7
 
8
8
  # Handle control expression `[:slim, :control, code, content]`
9
9
  #
@@ -48,11 +48,12 @@ module Slim
48
48
  end
49
49
  end
50
50
 
51
- # Handle text expression `[:slim, :text, content]`
51
+ # Handle text expression `[:slim, :text, type, content]`
52
52
  #
53
+ # @param [Symbol] type Text type
53
54
  # @param [Array] content Temple expression
54
55
  # @return [Array] Compiled temple expression
55
- def on_slim_text(content)
56
+ def on_slim_text(type, content)
56
57
  compile(content)
57
58
  end
58
59
  end
@@ -7,7 +7,7 @@ module Slim
7
7
  #
8
8
  # @api private
9
9
  class DoInserter < Filter
10
- BLOCK_REGEX = /(\A(if|unless|else|elsif|when|begin|rescue|ensure|case)\b)|do\s*(\|[^\|]*\|\s*)?\Z/
10
+ BLOCK_REGEX = /(\A(if|unless|else|elsif|when|begin|rescue|ensure|case)\b)|\bdo\s*(\|[^\|]*\|\s*)?\Z/
11
11
 
12
12
  # Handle control expression `[:slim, :control, code, content]`
13
13
  #
@@ -30,8 +30,7 @@ module Slim
30
30
  # @api private
31
31
  class OutputProtector < Filter
32
32
  def call(exp)
33
- @protect = []
34
- @collected = ''
33
+ @protect, @collected, @tag = [], '', "%#{object_id.abs.to_s(36)}%"
35
34
  super(exp)
36
35
  @collected
37
36
  end
@@ -42,16 +41,16 @@ module Slim
42
41
  end
43
42
 
44
43
  def on_slim_output(escape, text, content)
45
- @collected << "pro#{@protect.size}tect"
44
+ @collected << @tag
46
45
  @protect << [:slim, :output, escape, text, content]
47
46
  nil
48
47
  end
49
48
 
50
49
  def unprotect(text)
51
50
  block = [:multi]
52
- while text =~ /pro(\d+)tect/
51
+ while text =~ /#{@tag}/
53
52
  block << [:static, $`]
54
- block << @protect[$1.to_i]
53
+ block << @protect.shift
55
54
  text = $'
56
55
  end
57
56
  block << [:static, text]
@@ -63,25 +62,29 @@ module Slim
63
62
  class Embedded < Filter
64
63
  @engines = {}
65
64
 
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))
65
+ class << self
66
+ attr_reader :engines
67
+
68
+ # Register embedded engine
69
+ #
70
+ # @param [String] name Name of the engine
71
+ # @param [Class] klass Engine class
72
+ # @param option_filter List of options to pass to engine.
73
+ # Last argument can be default option hash.
74
+ def register(name, klass, *option_filter)
75
+ name = name.to_sym
76
+ local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
77
+ define_options(name, *option_filter)
78
+ klass.define_options(name)
79
+ engines[name.to_sym] = proc do |options|
80
+ klass.new({}.update(options).delete_if {|k,v| !option_filter.include?(k) && k != name }.update(local_options))
81
+ end
79
82
  end
80
- end
81
83
 
82
- def self.create(name, options)
83
- constructor = @engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found")
84
- constructor.call(options)
84
+ def create(name, options)
85
+ constructor = engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found")
86
+ constructor.call(options)
87
+ end
85
88
  end
86
89
 
87
90
  define_options :enable_engines, :disable_engines
@@ -252,6 +255,7 @@ module Slim
252
255
 
253
256
  # These engines are executed at compile time
254
257
  register :coffee, JavaScriptEngine, :engine => StaticTiltEngine
258
+ register :opal, JavaScriptEngine, :engine => StaticTiltEngine
255
259
  register :less, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
256
260
  register :styl, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
257
261
  register :sass, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
@@ -10,7 +10,7 @@ module Slim
10
10
  #
11
11
  # @api private
12
12
  class EndInserter < Filter
13
- IF_RE = /\A(if|unless|else|elsif|when|rescue|ensure)\b|do\s*(\|[^\|]*\|)?\s*$/
13
+ IF_RE = /\A(if|unless|else|elsif|when|rescue|ensure)\b|\bdo\s*(\|[^\|]*\|)?\s*$/
14
14
  ELSE_RE = /\A(else|elsif|when|rescue|ensure)\b/
15
15
  END_RE = /\Aend\b/
16
16
 
@@ -12,13 +12,14 @@ module Slim
12
12
  :merge_attrs => {'class' => ' '},
13
13
  :generator => Temple::Generators::ArrayBuffer,
14
14
  :default_tag => 'div'
15
+ define_deprecated_options :attr_delims
15
16
 
16
17
  filter :Encoding, :encoding
17
18
  filter :RemoveBOM
18
- use Slim::Parser, :file, :tabsize, :shortcut, :default_tag, :attr_delims
19
+ use Slim::Parser, :file, :tabsize, :shortcut, :default_tag, :attr_delims, :attr_list_delims, :code_attr_delims
19
20
  use Slim::Embedded, :enable_engines, :disable_engines, :pretty
20
21
  use Slim::Interpolation
21
- use Slim::Splat::Filter, :merge_attrs, :attr_quote, :sort_attrs, :default_tag, :hyphen_attrs, :format
22
+ use Slim::Splat::Filter, :merge_attrs, :attr_quote, :sort_attrs, :default_tag, :hyphen_attrs, :format, :use_html_safe
22
23
  use Slim::DoInserter
23
24
  use Slim::EndInserter
24
25
  use Slim::Controls, :disable_capture
@@ -8,8 +8,8 @@ module Slim
8
8
  # @api private
9
9
  class Filter < Temple::HTML::Filter
10
10
  # Pass-through handler
11
- def on_slim_text(content)
12
- [:slim, :text, compile(content)]
11
+ def on_slim_text(type, content)
12
+ [:slim, :text, type, compile(content)]
13
13
  end
14
14
 
15
15
  # Pass-through handler
@@ -4,12 +4,14 @@ module Slim
4
4
  module Grammar
5
5
  extend Temple::Grammar
6
6
 
7
+ TextTypes << :verbatim | :explicit | :implicit | :inline
8
+
7
9
  Expression <<
8
10
  [:slim, :control, String, Expression] |
9
11
  [:slim, :output, Bool, String, Expression] |
10
12
  [:slim, :interpolate, String] |
11
13
  [:slim, :embedded, String, Expression] |
12
- [:slim, :text, Expression] |
14
+ [:slim, :text, TextTypes, Expression] |
13
15
  [:slim, :attrvalue, Bool, String]
14
16
 
15
17
  HTMLAttr <<
@@ -0,0 +1,54 @@
1
+ require 'slim'
2
+
3
+ module Slim
4
+ # Handles inlined includes
5
+ #
6
+ # Slim files are compiled, non-Slim files are included as text with `#{interpolation}`
7
+ #
8
+ # @api private
9
+ class Include < Slim::Filter
10
+ define_options :file, :include_dirs => [Dir.pwd, '.']
11
+
12
+ def on_html_tag(tag, attributes, content)
13
+ return super if tag != 'include'
14
+ name = content.flatten.select {|s| String === s }.join
15
+ raise ArgumentError, 'Invalid include statement' unless attributes == [:html, :attrs] && !name.empty?
16
+ unless file = find_file(name)
17
+ name = "#{name}.slim" if name !~ /\.slim\Z/i
18
+ file = find_file(name)
19
+ end
20
+ raise Temple::FilterError, "'#{name}' not found in #{options[:include_dirs].join(':')}" unless file
21
+ content = File.read(file)
22
+ if file =~ /\.slim\Z/i
23
+ Thread.current[:slim_include_engine].call(content)
24
+ else
25
+ [:slim, :interpolate, content]
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ def find_file(name)
32
+ current_dir = File.dirname(File.expand_path(options[:file]))
33
+ options[:include_dirs].map {|dir| File.expand_path(File.join(dir, name), current_dir) }.find {|file| File.file?(file) }
34
+ end
35
+ end
36
+
37
+ class Engine
38
+ after Slim::Parser, Slim::Include, :file, :include_dirs
39
+ after Slim::Include, :stop do |exp|
40
+ throw :stop, exp if Thread.current[:slim_include_level] > 1
41
+ exp
42
+ end
43
+
44
+ alias call_without_include call
45
+ def call(input)
46
+ Thread.current[:slim_include_engine] = self
47
+ Thread.current[:slim_include_level] ||= 0
48
+ Thread.current[:slim_include_level] += 1
49
+ catch(:stop) { call_without_include(input) }
50
+ ensure
51
+ Thread.current[:slim_include_engine] = nil if (Thread.current[:slim_include_level] -= 1) == 0
52
+ end
53
+ end
54
+ end
@@ -23,7 +23,7 @@ module Slim
23
23
  string, code = parse_expression($')
24
24
  escape = code !~ /\A\{.*\}\Z/
25
25
  block << [:slim, :output, escape, escape ? code : code[1..-2], [:multi]]
26
- when /\A([#\\]|[^#\\]*)/
26
+ when /\A([#\\]?[^#\\]*([#\\][^\\#\{][^#\\]*)*)/
27
27
  # Static text
28
28
  block << [:static, $&]
29
29
  string = $'
@@ -5,6 +5,8 @@ module Slim
5
5
  class Parser < Temple::Parser
6
6
  define_options :file,
7
7
  :default_tag,
8
+ :code_attr_delims,
9
+ :attr_list_delims,
8
10
  :tabsize => 4,
9
11
  :shortcut => {
10
12
  '#' => { :attr => 'id' },
@@ -14,7 +16,7 @@ module Slim
14
16
  '(' => ')',
15
17
  '[' => ']',
16
18
  '{' => '}',
17
- }
19
+ }
18
20
 
19
21
  class SyntaxError < StandardError
20
22
  attr_reader :error, :file, :line, :lineno, :column
@@ -40,6 +42,8 @@ module Slim
40
42
 
41
43
  def initialize(opts = {})
42
44
  super
45
+ @attr_list_delims = options[:attr_list_delims] || options[:attr_delims]
46
+ @code_attr_delims = options[:code_attr_delims] || options[:attr_delims]
43
47
  tabsize = options[:tabsize]
44
48
  if tabsize > 1
45
49
  @tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize-1}}\t/
@@ -61,9 +65,12 @@ module Slim
61
65
  @attr_shortcut_re = /\A(#{keys}+)((?:#{WORD_RE}|-)*)/
62
66
  keys = Regexp.union @tag_shortcut.keys.sort_by {|k| -k.size }
63
67
  @tag_re = /\A(?:#{keys}|\*(?=[^\s]+)|(#{WORD_RE}(?:#{WORD_RE}|:|-)*#{WORD_RE}|#{WORD_RE}+))/
64
- keys = Regexp.escape options[:attr_delims].keys.join
65
- @delim_re = /\A[#{keys}]/
66
- @attr_delim_re = /\A\s*([#{keys}])/
68
+ keys = Regexp.escape @code_attr_delims.keys.join
69
+ @code_attr_delims_re = /\A[#{keys}]/
70
+ keys = Regexp.escape @attr_list_delims.keys.join
71
+ @attr_list_delims_re = /\A\s*([#{keys}])/
72
+ # Access available engine keys to allow nicer one-line syntax
73
+ @embedded_re = /\A(#{Regexp.union(Embedded.engines.keys.map(&:to_s))}):(\s*)/
67
74
  end
68
75
 
69
76
  # Compile string to Temple expression
@@ -83,6 +90,7 @@ module Slim
83
90
  protected
84
91
 
85
92
  WORD_RE = ''.respond_to?(:encoding) ? '\p{Word}' : '\w'
93
+ LC_WORD_RE = '[_a-z0-9]'
86
94
  ATTR_NAME = "\\A\\s*(#{WORD_RE}(?:#{WORD_RE}|:|-)*)"
87
95
  QUOTED_ATTR_RE = /#{ATTR_NAME}\s*=(=?)\s*("|')/
88
96
  CODE_ATTR_RE = /#{ATTR_NAME}\s*=(=?)\s*/
@@ -100,7 +108,7 @@ module Slim
100
108
  #
101
109
  # We uses this information to figure out how many steps we must "jump"
102
110
  # out when we see an de-indented line.
103
- @indents = [0]
111
+ @indents = []
104
112
 
105
113
  # Whenever we want to output something, we'll *always* output it to the
106
114
  # last stack in this array. So when there's a line that expects
@@ -138,6 +146,9 @@ module Slim
138
146
 
139
147
  indent = get_indent(@line)
140
148
 
149
+ # Choose first indentation yourself
150
+ @indents << indent if @indents.empty?
151
+
141
152
  # Remove the indentation
142
153
  @line.lstrip!
143
154
 
@@ -159,7 +170,7 @@ module Slim
159
170
  # This line was deindented.
160
171
  # Now we're have to go through the all the indents and figure out
161
172
  # how many levels we've deindented.
162
- while indent < @indents.last
173
+ while indent < @indents.last && @indents.size > 1
163
174
  @indents.pop
164
175
  @stacks.pop
165
176
  end
@@ -180,7 +191,7 @@ module Slim
180
191
  case @line
181
192
  when /\A\/!( ?)/
182
193
  # HTML comment
183
- @stacks.last << [:html, :comment, [:slim, :text, parse_text_block($', @indents.last + $1.size + 2)]]
194
+ @stacks.last << [:html, :comment, [:slim, :text, :verbatim, parse_text_block($', @indents.last + $1.size + 2)]]
184
195
  when /\A\/\[\s*(.*?)\s*\]\s*\Z/
185
196
  # HTML conditional comment
186
197
  block = [:multi]
@@ -190,9 +201,9 @@ module Slim
190
201
  # Slim comment
191
202
  parse_comment_block
192
203
  when /\A([\|'])( ?)/
193
- # Found a text block.
204
+ # Found verbatim text block.
194
205
  trailing_ws = $1 == "'"
195
- @stacks.last << [:slim, :text, parse_text_block($', @indents.last + $2.size + 1)]
206
+ @stacks.last << [:slim, :text, :verbatim, parse_text_block($', @indents.last + $2.size + 1)]
196
207
  @stacks.last << [:static, ' '] if trailing_ws
197
208
  when /\A</
198
209
  # Inline html
@@ -216,10 +227,10 @@ module Slim
216
227
  @stacks.last << [:slim, :output, $1.empty?, parse_broken_line, block]
217
228
  @stacks.last << [:static, ' '] if trailing_ws
218
229
  @stacks << block
219
- when /\A(\w+):\s*\Z/
230
+ when @embedded_re
220
231
  # Embedded template detected. It is treated as block.
221
- @stacks.last << [:slim, :embedded, $1, parse_text_block]
222
- when /\Adoctype\s+/i
232
+ @stacks.last << [:slim, :embedded, $1, parse_text_block($', @orig_line.size - $'.size + $2.size)]
233
+ when /\Adoctype\s+/
223
234
  # Found doctype declaration
224
235
  @stacks.last << [:html, :doctype, $'.strip]
225
236
  when @tag_re
@@ -227,11 +238,15 @@ module Slim
227
238
  @line = $' if $1
228
239
  parse_tag($&)
229
240
  else
230
- syntax_error! 'Unknown line indicator'
241
+ unknown_line_indicator
231
242
  end
232
243
  @stacks.last << [:newline]
233
244
  end
234
245
 
246
+ def unknown_line_indicator
247
+ syntax_error! 'Unknown line indicator'
248
+ end
249
+
235
250
  def parse_comment_block
236
251
  while !@lines.empty? && (@lines.first =~ /\A\s*\Z/ || get_indent(@lines.first) > @indents.last)
237
252
  next_line
@@ -239,7 +254,7 @@ module Slim
239
254
  end
240
255
  end
241
256
 
242
- def parse_text_block(first_line = nil, text_indent = nil, in_tag = false)
257
+ def parse_text_block(first_line = nil, text_indent = nil)
243
258
  result = [:multi]
244
259
  if !first_line || first_line.empty?
245
260
  text_indent = nil
@@ -269,11 +284,9 @@ module Slim
269
284
  # as deep as the first line.
270
285
  offset = text_indent ? indent - text_indent : 0
271
286
  if offset < 0
272
- syntax_error!("Text line not indented deep enough.\n" +
273
- "The first text line defines the necessary text indentation." +
274
- (in_tag ? "\nAre you trying to nest a child tag in a tag containing text? Use | for the text block!" : ''))
287
+ text_indent += offset
288
+ offset = 0
275
289
  end
276
-
277
290
  result << [:newline] << [:slim, :interpolate, (text_indent ? "\n" : '') + (' ' * offset) + @line]
278
291
 
279
292
  # The indentation of first line of the text block
@@ -326,19 +339,22 @@ module Slim
326
339
  when /\A\s*:\s*/
327
340
  # Block expansion
328
341
  @line = $'
329
- (@line =~ @tag_re) || syntax_error!('Expected tag')
330
- @line = $' if $1
331
- content = [:multi]
332
- tag << content
333
- i = @stacks.size
334
- @stacks << content
335
- parse_tag($&)
336
- @stacks.delete_at(i)
342
+ if @line =~ @embedded_re
343
+ tag << [:slim, :embedded, $1, parse_text_block($', @orig_line.size - @line.size + $2.size)]
344
+ else
345
+ (@line =~ @tag_re) || syntax_error!('Expected tag')
346
+ @line = $' if $1
347
+ content = [:multi]
348
+ tag << content
349
+ i = @stacks.size
350
+ @stacks << content
351
+ parse_tag($&)
352
+ @stacks.delete_at(i)
353
+ end
337
354
  when /\A\s*=(=?)(['<>]*)/
338
355
  # Handle output code
339
356
  @line = $'
340
357
  trailing_ws2 = $2.include?('\'') || $2.include?('>')
341
- leading_ws2 = $2.include?('<')
342
358
  block = [:multi]
343
359
  @stacks.last.insert(-2, [:static, ' ']) if !leading_ws && $2.include?('<')
344
360
  tag << [:slim, :output, $1 != '=', parse_broken_line, block]
@@ -353,17 +369,17 @@ module Slim
353
369
  content = [:multi]
354
370
  tag << content
355
371
  @stacks << content
356
- when /\A( ?)(.*)\Z/
372
+ when /\A ?/
357
373
  # Text content
358
- tag << [:slim, :text, parse_text_block($2, @orig_line.size - @line.size + $1.size, true)]
374
+ tag << [:slim, :text, :inline, parse_text_block($', @orig_line.size - $'.size)]
359
375
  end
360
376
  end
361
377
 
362
378
  def parse_attributes(attributes)
363
379
  # Check to see if there is a delimiter right after the tag name
364
380
  delimiter = nil
365
- if @line =~ @attr_delim_re
366
- delimiter = options[:attr_delims][$1]
381
+ if @line =~ @attr_list_delims_re
382
+ delimiter = @attr_list_delims[$1]
367
383
  @line = $'
368
384
  end
369
385
 
@@ -434,9 +450,9 @@ module Slim
434
450
  elsif @line[0] == close_delimiter[0]
435
451
  count -= 1
436
452
  end
437
- elsif @line =~ @delim_re
453
+ elsif @line =~ @code_attr_delims_re
438
454
  count = 1
439
- delimiter, close_delimiter = $&, options[:attr_delims][$&]
455
+ delimiter, close_delimiter = $&, @code_attr_delims[$&]
440
456
  end
441
457
  code << @line.slice!(0)
442
458
  end
@@ -448,27 +464,21 @@ module Slim
448
464
  def parse_quoted_attribute(quote)
449
465
  value, count = '', 0
450
466
 
451
- until @line.empty? || (count == 0 && @line[0] == quote[0])
452
- if @line =~ /\A\\\Z/
453
- value << ' '
467
+ until count == 0 && @line[0] == quote[0]
468
+ if @line =~ /\A(\\)?\Z/
469
+ value << ($1 ? ' ' : "\n")
454
470
  expect_next_line
455
471
  else
456
- if count > 0
457
- if @line[0] == ?{
458
- count += 1
459
- elsif @line[0] == ?}
460
- count -= 1
461
- end
462
- elsif @line =~ /\A#\{/
463
- value << @line.slice!(0)
464
- count = 1
472
+ if @line[0] == ?{
473
+ count += 1
474
+ elsif @line[0] == ?}
475
+ count -= 1
465
476
  end
466
477
  value << @line.slice!(0)
467
478
  end
468
479
  end
469
480
 
470
481
  syntax_error!("Expected closing brace }") if count != 0
471
- syntax_error!("Expected closing quote #{quote}") if @line[0] != quote[0]
472
482
  @line.slice!(0)
473
483
 
474
484
  value
@@ -478,6 +488,11 @@ module Slim
478
488
  def syntax_error!(message)
479
489
  raise SyntaxError.new(message, options[:file], @orig_line, @lineno,
480
490
  @orig_line && @line ? @orig_line.size - @line.size : 0)
491
+ rescue SyntaxError => ex
492
+ # HACK: Manipulate stacktrace for Rails and other frameworks
493
+ # to find the right file.
494
+ ex.backtrace.unshift "#{options[:file]}:#{@lineno}"
495
+ raise
481
496
  end
482
497
 
483
498
  def expect_next_line