slim 0.7.0.beta.2 → 0.7.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.
@@ -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