slim 0.7.0.beta.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,23 @@
1
1
  module Slim
2
+ # Parses Slim code and transforms it to a Temple expression
3
+ # @api private
2
4
  class Parser
3
5
  class SyntaxError < StandardError
4
- def initialize(message, line, lineno, column = 0)
5
- @message = message
6
+ attr_reader :error, :file, :line, :lineno, :column
7
+
8
+ def initialize(error, file, line, lineno, column = 0)
9
+ @error = error
10
+ @file = file || '(__TEMPLATE__)'
6
11
  @line = line.strip
7
12
  @lineno = lineno
8
13
  @column = column
9
14
  end
10
15
 
11
16
  def to_s
12
- %{#{@message}
13
- Line #{@lineno}
14
- #{@line}
15
- #{' ' * @column}^
17
+ %{#{error}
18
+ #{file}, Line #{lineno}
19
+ #{line}
20
+ #{' ' * column}^
16
21
  }
17
22
  end
18
23
  end
@@ -24,6 +29,10 @@ module Slim
24
29
  @tab = ' ' * (options[:tabsize] || 4)
25
30
  end
26
31
 
32
+ # Compile string to Temple expression
33
+ #
34
+ # @param [String] str Slim code
35
+ # @return [Array] Temple expression representing the code
27
36
  def compile(str)
28
37
  lineno = 0
29
38
  result = [:multi]
@@ -58,7 +67,7 @@ module Slim
58
67
  # Hello
59
68
  # World!
60
69
  #
61
- text_indent, text_base_indent = nil, nil
70
+ block_indent, in_comment, text_indent = nil, false, nil
62
71
 
63
72
  str.each_line do |line|
64
73
  lineno += 1
@@ -69,12 +78,20 @@ module Slim
69
78
  # Handle broken lines
70
79
  if broken_line
71
80
  if broken_line[-1] == ?\\
72
- broken_line << "\n#{line}"
81
+ broken_line << "\n" << line
73
82
  next
74
83
  end
75
84
  broken_line = nil
76
85
  end
77
86
 
87
+ if line.strip.empty?
88
+ # This happens to be an empty line, so we'll just have to make sure
89
+ # the generated code includes a newline (so the line numbers in the
90
+ # stack trace for an exception matches the ones in the template).
91
+ stacks.last << [:newline]
92
+ next
93
+ end
94
+
78
95
  # Figure out the indentation. Kinda ugly/slow way to support tabs,
79
96
  # but remember that this is only done at parsing time.
80
97
  indent = line[/^[ \t]*/].gsub("\t", @tab).size
@@ -82,41 +99,33 @@ module Slim
82
99
  # Remove the indentation
83
100
  line.lstrip!
84
101
 
85
- if line.strip.empty? || line[0] == ?/
86
- # This happens to be an empty line or a comment, so we'll just have to make sure
87
- # the generated code includes a newline (so the line numbers in the
88
- # stack trace for an exception matches the ones in the template).
89
- stacks.last << [:newline]
90
- next
91
- end
102
+ # Handle blocks with multiple lines
103
+ if block_indent
104
+ if indent > block_indent
105
+ # This line happens to be indented deeper (or equal) than the block start character (|, ', `, /).
106
+ # This means that it's a part of the block.
92
107
 
93
- # Handle text blocks with multiple lines
94
- if text_indent
95
- if indent > text_indent
96
- # This line happens to be indented deeper (or equal) than the block start character (|, ', `).
97
- # This means that it's a part of the text block.
108
+ if !in_comment
109
+ # The indentation of first line of the text block determines the text base indentation.
110
+ text_indent ||= indent
98
111
 
99
- # The indentation of first line of the text block determines the text base indentation.
100
- text_base_indent ||= indent
112
+ # The text block lines must be at least indented as deep as the first line.
113
+ offset = indent - text_indent
114
+ syntax_error! 'Unexpected text indentation', line, lineno if offset < 0
101
115
 
102
- # The text block lines must be at least indented as deep as the first line.
103
- offset = indent - text_base_indent
104
- syntax_error! 'Unexpected text indentation', line, lineno if offset < 0
116
+ # Generate the additional spaces in front.
117
+ i = ' ' * offset
118
+ stacks.last << [:slim, :text, i + line]
119
+ end
105
120
 
106
- # Generate the additional spaces in front.
107
- i = ' ' * offset
108
- stacks.last << [:slim, :text, i + line]
109
121
  stacks.last << [:newline]
110
-
111
- # Mark this line as it's been indented as the text block start character.
112
- indent = text_indent
113
-
114
122
  next
115
123
  end
116
124
 
117
- # It's guaranteed that we're now *not* in a text block, because
118
- # the indent will always be set to the text block start indent.
119
- text_indent = text_base_indent = nil
125
+ # It's guaranteed that we're now *not* in a block, because
126
+ # the indent was less than the block start indent.
127
+ block_indent = text_indent = nil
128
+ in_comment = false
120
129
  end
121
130
 
122
131
  # If there's more stacks than indents, it means that the previous
@@ -152,20 +161,21 @@ module Slim
152
161
  end
153
162
 
154
163
  case line[0]
155
- when ?|, ?', ?`
156
- # Found a piece of text.
164
+ when ?|, ?', ?`, ?/
165
+ # Found a block.
157
166
 
158
167
  # We're now expecting the next line to be indented, so we'll need
159
168
  # to push a block to the stack.
160
169
  block = [:multi]
161
170
  stacks.last << block
162
171
  stacks << block
163
- text_indent = indent
172
+ block_indent = indent
164
173
 
174
+ in_comment = line[0] == ?/
165
175
  line.slice!(0)
166
- if !line.strip.empty?
176
+ if !in_comment && !line.strip.empty?
167
177
  block << [:slim, :text, line.sub(/^( )/, '')]
168
- text_base_indent = text_indent + ($1 ? 2 : 1)
178
+ text_indent = block_indent + ($1 ? 2 : 1)
169
179
  end
170
180
  when ?-, ?=
171
181
  # Found a potential code block.
@@ -191,18 +201,24 @@ module Slim
191
201
  stacks.last << [:slim, :directive, line[1..-1].strip]
192
202
  else
193
203
  if line =~ /^(\w+):\s*$/
194
- # Embedded template detected. It is treated like a text block.
204
+ # Embedded template detected. It is treated as block.
195
205
  block = [:slim, :embedded, $1]
196
- stacks.last << block
206
+ stacks.last << [:newline] << block
197
207
  stacks << block
198
- text_indent = indent
208
+ block_indent = indent
209
+ next
199
210
  else
200
211
  # Found a HTML tag.
201
- exp, content, broken_line = parse_tag(line, lineno)
202
- stacks.last << exp
203
- stacks << content if content
212
+ tag, block, broken_line, text_indent = parse_tag(line, lineno)
213
+ stacks.last << tag
214
+ stacks << block if block
215
+ if text_indent
216
+ block_indent = indent
217
+ text_indent += indent
218
+ end
204
219
  end
205
220
  end
221
+ stacks.last << [:newline]
206
222
  end
207
223
 
208
224
  result
@@ -210,31 +226,32 @@ module Slim
210
226
 
211
227
  private
212
228
 
213
- ATTR_REGEX = /^ ([\w-]+)=/
229
+ ATTR_REGEX = /^ (\w[:\w-]*)=/
214
230
  QUOTED_VALUE_REGEX = /^("[^"]+"|'[^']+')/
215
231
  ATTR_SHORTHAND = {
216
232
  '#' => 'id',
217
233
  '.' => 'class',
218
- }
234
+ }.freeze
219
235
  DELIMITERS = {
220
236
  '(' => ')',
221
237
  '[' => ']',
222
238
  '{' => '}',
223
- }
239
+ }.freeze
224
240
  DELIMITER_REGEX = /^([\(\[\{])/
225
241
  CLOSE_DELIMITER_REGEX = /^([\)\]\}])/
226
242
  if RUBY_VERSION > '1.9'
227
243
  CLASS_ID_REGEX = /^(#|\.)([\w\u00c0-\uFFFF][\w:\u00c0-\uFFFF-]*)/
228
244
  else
229
- CLASS_ID_REGEX = /^(#|\.)([\w][\w:-]*)/
245
+ CLASS_ID_REGEX = /^(#|\.)(\w[\w:-]*)/
230
246
  end
231
247
 
232
248
  def parse_tag(line, lineno)
233
249
  orig_line = line
234
250
 
235
- if line =~ /^(#|\.)/
251
+ case line
252
+ when /^(#|\.)/
236
253
  tag = 'div'
237
- elsif line =~ /^[\w:]+/
254
+ when /^\w[:\w-]*/
238
255
  tag = $&
239
256
  line = $'
240
257
  else
@@ -248,7 +265,7 @@ module Slim
248
265
 
249
266
  # Find any literal class/id attributes
250
267
  while line =~ CLASS_ID_REGEX
251
- attributes << [ATTR_SHORTHAND[$1], $2]
268
+ attributes << [ATTR_SHORTHAND[$1], false, $2]
252
269
  line = $'
253
270
  end
254
271
 
@@ -265,14 +282,14 @@ module Slim
265
282
  key = $1
266
283
  line = $'
267
284
  if line =~ QUOTED_VALUE_REGEX
268
- # Value is quote (static)
285
+ # Value is quoted (static)
269
286
  line = $'
270
- value = $1[1..-2]
287
+ attributes << [key, false, $1[1..-2]]
271
288
  else
272
289
  # Value is ruby code
273
290
  line, value = parse_ruby_attribute(orig_line, line, lineno, delimiter)
291
+ attributes << [key, true, value]
274
292
  end
275
- attributes << [key, value]
276
293
  end
277
294
 
278
295
  # Find ending delimiter
@@ -285,26 +302,22 @@ module Slim
285
302
  end
286
303
 
287
304
  content = [:multi]
288
- broken_line = nil
305
+ tag = [:slim, :tag, tag, attributes, content]
289
306
 
290
- if line.strip.empty?
291
- # If the line was empty there might be some indented content in the
292
- # lines beneath it. We'll handle this by making this method return
293
- # the block-variable. #compile will then push this onto the
294
- # stacks-array.
295
- block = content
296
- elsif line =~ /^\s*=(=?)/
297
- # Output
307
+ if line =~ /^\s*=(=?)/
308
+ # Handle output code
298
309
  block = [:multi]
299
310
  broken_line = $'.strip
300
311
  content << [:slim, :output, $1 != '=', broken_line, block]
312
+ [tag, block, broken_line, nil]
313
+ elsif !line.empty?
314
+ # Handle text content
315
+ content << [:slim, :text, line.sub(/^( )/, '')]
316
+ [tag, content, nil, orig_line.size - line.size + ($1 ? 1 : 0)]
301
317
  else
302
- # Text content
303
- line.sub!(/^ /, '')
304
- content << [:slim, :text, line]
318
+ # Empty line
319
+ [tag, content, nil, nil]
305
320
  end
306
-
307
- return [:slim, :tag, tag, attributes, content], block, broken_line
308
321
  end
309
322
 
310
323
  def parse_ruby_attribute(orig_line, line, lineno, delimiter)
@@ -344,12 +357,12 @@ module Slim
344
357
  # e.g id=[hash[:a] + hash[:b]]
345
358
  value = value[1..-2] if value =~ DELIMITER_REGEX && DELIMITERS[value[0, 1]] == value[-1, 1]
346
359
 
347
- [line, '#{%s}' % value]
360
+ return line, value
348
361
  end
349
362
 
350
363
  # A little helper for raising exceptions.
351
- def syntax_error!(*args)
352
- raise SyntaxError.new(*args)
364
+ def syntax_error!(message, *args)
365
+ raise SyntaxError.new(message, @options[:file], *args)
353
366
  end
354
367
  end
355
368
  end
@@ -2,11 +2,12 @@ require 'slim'
2
2
 
3
3
  module ActionView
4
4
  module TemplateHandlers
5
+ # Slim handler for Rails 3
5
6
  class SlimHandler < TemplateHandler
6
7
  include Compilable
7
8
 
8
9
  def compile(template)
9
- return Slim::Engine.new(template.source, use_html_safe: true).prepare
10
+ Slim::Engine.new(:use_html_safe => true).compile(template.source)
10
11
  end
11
12
  end
12
13
  end
@@ -1,14 +1,34 @@
1
1
  module Slim
2
+ # Tilt template implementation for Slim
3
+ # @api public
2
4
  class Template < Tilt::Template
5
+ # Prepare Slim template
6
+ #
7
+ # Called immediately after template data is loaded.
8
+ #
9
+ # @return [void]
3
10
  def prepare
4
- @src = Engine.new(options).compile(data)
11
+ @src = Engine.new(options.merge(:file => eval_file)).compile(data)
5
12
  end
6
13
 
14
+ # Process the template and return the result.
15
+ #
16
+ # Template executationis guaranteed to be performed in the scope object with the locals
17
+ # specified and with support for yielding to the block.
18
+ #
19
+ # @param [Object] scope Scope object where the code is evaluated
20
+ # @param [Hash] locals Local variables
21
+ # @yield Block given to the template code
22
+ # @return [String] Evaluated template
7
23
  def evaluate(scope, locals, &block)
8
24
  scope.instance_eval { extend Slim::Helpers } if options[:helpers]
9
25
  super
10
26
  end
11
27
 
28
+ # A string containing the (Ruby) source code for the template.
29
+ #
30
+ # @param [Hash] locals Local variables
31
+ # @return [String] Compiled template ruby code
12
32
  def precompiled_template(locals)
13
33
  @src
14
34
  end
@@ -1,3 +1,3 @@
1
1
  module Slim
2
- VERSION = "0.7.0.beta.2"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -5,8 +5,8 @@ Gem::Specification.new do |s|
5
5
  s.name = "slim"
6
6
  s.version = Slim::VERSION
7
7
  s.date = Date.today.to_s
8
- s.authors = ["Andrew Stone", "Fred Wu"]
9
- s.email = ["andy@stonean.com", "ifredwu@gmail.com"]
8
+ s.authors = ["Andrew Stone", "Fred Wu", "Daniel Mendler"]
9
+ s.email = ["andy@stonean.com", "ifredwu@gmail.com", "mail@daniel-mendler.de"]
10
10
  s.summary = %q{Slim is a template language.}
11
11
  s.description = %q{Slim is a template language whose goal is reduce the syntax to the essential parts without becoming cryptic.}
12
12
  s.homepage = %q{http://github.com/stonean/slim}
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.require_paths = ["lib"]
22
22
 
23
23
  s.add_runtime_dependency(%q<escape_utils>, [">= 0.1.9"]) unless RUBY_PLATFORM == "java"
24
- s.add_runtime_dependency(%q<temple>, ["~> 0.1.2"])
24
+ s.add_runtime_dependency(%q<temple>, ["~> 0.1.3"])
25
25
  s.add_runtime_dependency(%q<tilt>, ["~> 1.1"])
26
26
  s.add_development_dependency(%q<rake>, [">= 0.8.7"])
27
27
  s.add_development_dependency(%q<haml>, [">= 0"])
@@ -30,4 +30,5 @@ Gem::Specification.new do |s|
30
30
  s.add_development_dependency(%q<rcov>, [">= 0"])
31
31
  s.add_development_dependency(%q<rdiscount>, [">= 0"])
32
32
  s.add_development_dependency(%q<liquid>, [">= 0"])
33
+ s.add_development_dependency(%q<yard>, [">= 0"])
33
34
  end
@@ -14,18 +14,36 @@ class TestSlim < MiniTest::Unit::TestCase
14
14
 
15
15
  def teardown
16
16
  String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?)
17
+ String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe)
18
+ Object.send(:undef_method, :html_safe?) if Object.method_defined?(:html_safe?)
17
19
  Slim::Filter::DEFAULT_OPTIONS.delete(:use_html_safe)
18
20
  end
19
21
 
20
22
  def assert_html(expected, source, options = {})
21
- assert_equal expected, Slim::Engine.new(source, options).render(@env)
23
+ assert_equal expected, Slim::Template.new(options[:file], options) { source }.render(@env)
22
24
  end
23
25
 
24
26
  def assert_syntax_error(message, source, options = {})
25
- Slim::Engine.new(source, options).render(@env)
27
+ Slim::Template.new(options[:file], options) { source }.render(@env)
26
28
  raise 'Syntax error expected'
27
29
  rescue Slim::Parser::SyntaxError => ex
28
- assert_equal ex.message, message
30
+ assert_equal message, ex.message
31
+ end
32
+
33
+ def assert_ruby_error(error, from, source, options = {})
34
+ Slim::Template.new(options[:file], options) { source }.render(@env)
35
+ raise 'Ruby error expected'
36
+ rescue error => ex
37
+ ex.backtrace[0] =~ /^(.*?:\d+):/
38
+ assert_equal from, $1
39
+ end
40
+
41
+ def assert_ruby_syntax_error(from, source, options = {})
42
+ Slim::Template.new(options[:file], options) { source }.render(@env)
43
+ raise 'Ruby syntax error expected'
44
+ rescue SyntaxError => ex
45
+ ex.message =~ /^(.*?:\d+):/
46
+ assert_equal from, $1
29
47
  end
30
48
  end
31
49
 
@@ -157,13 +157,13 @@ p id=(1 + 1)*5 Test it
157
157
  def test_interpolation_in_text
158
158
  source = %q{
159
159
  p
160
- | #{hello_world}
160
+ | #{hello_world} with "quotes"
161
161
  p
162
162
  |
163
163
  A message from the compiler: #{hello_world}
164
164
  }
165
165
 
166
- assert_html '<p>Hello World from @env</p><p>A message from the compiler: Hello World from @env</p>', source
166
+ assert_html '<p>Hello World from @env with "quotes"</p><p>A message from the compiler: Hello World from @env</p>', source
167
167
  end
168
168
 
169
169
  def test_interpolation_in_tag
@@ -1,6 +1,12 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestSlimHelpers < TestSlim
4
+ class HtmlSafeString < String
5
+ def html_safe?
6
+ true
7
+ end
8
+ end
9
+
4
10
  def test_list_of
5
11
  source = %q{
6
12
  == list_of([1, 2, 3]) do |i|
@@ -9,4 +15,16 @@ class TestSlimHelpers < TestSlim
9
15
 
10
16
  assert_html "<li>1</li>\n<li>2</li>\n<li>3</li>", source, :helpers => true
11
17
  end
18
+
19
+ def test_list_of_with_html_safe
20
+ Object.send(:define_method, :html_safe?) { false }
21
+ String.send(:define_method, :html_safe) { HtmlSafeString.new(self) }
22
+
23
+ source = %q{
24
+ = list_of([1, 2, 3]) do |i|
25
+ = i
26
+ }
27
+
28
+ html = Slim::Template.new(:helpers => true, :use_html_safe => true) { source }.render(@env)
29
+ end
12
30
  end