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
@@ -0,0 +1,8 @@
1
+ require 'slim'
2
+ require 'slim/smart/filter'
3
+ require 'slim/smart/escaper'
4
+ require 'slim/smart/parser'
5
+
6
+ Slim::Engine.replace(Slim::Parser, Slim::Smart::Parser, :file, :tabsize, :shortcut, :default_tag, :attr_delims, :attr_list_delims, :code_attr_delims, :implicit_text)
7
+ Slim::Engine.after(Slim::Smart::Parser, Slim::Smart::Filter, :smart_text, :smart_text_end_chars, :smart_text_begin_chars)
8
+ Slim::Engine.after(Slim::Interpolation, Slim::Smart::Escaper, :smart_text_escaping)
@@ -0,0 +1,42 @@
1
+ module Slim
2
+ module Smart
3
+ # Perform smart entity escaping in the
4
+ # expressions `[:slim, :text, type, Expression]`.
5
+ #
6
+ # @api private
7
+ class Escaper < ::Slim::Filter
8
+ define_options :smart_text_escaping => true
9
+
10
+ def call(exp)
11
+ if options[:smart_text_escaping]
12
+ super
13
+ else
14
+ exp
15
+ end
16
+ end
17
+
18
+ def on_slim_text(type, content)
19
+ [:escape, type != :verbatim, [:slim, :text, type, compile(content)]]
20
+ end
21
+
22
+ def on_static(string)
23
+ # Prevent obvious &foo; and &#1234; and &#x00ff; entities from escaping.
24
+ block = [:multi]
25
+ until string.empty?
26
+ case string
27
+ when /\A&(\w+|#x[0-9a-f]+|#\d+);/i
28
+ # Entity.
29
+ block << [:escape, false, [:static, $&]]
30
+ string = $'
31
+ when /\A&?[^&]*/
32
+ # Other text.
33
+ block << [:static, $&]
34
+ string = $'
35
+ end
36
+ end
37
+ block
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,96 @@
1
+ module Slim
2
+ module Smart
3
+ # Perform newline processing in the
4
+ # expressions `[:slim, :text, type, Expression]`.
5
+ #
6
+ # @api private
7
+ class Filter < ::Slim::Filter
8
+ define_options :smart_text => true,
9
+ :smart_text_end_chars => '([{',
10
+ :smart_text_begin_chars => ',.;:!?)]}'
11
+
12
+ def initialize(opts = {})
13
+ super
14
+ @active = @prepend = @append = false
15
+ @prepend_re = /\A#{chars_re(options[:smart_text_begin_chars])}/
16
+ @append_re = /#{chars_re(options[:smart_text_end_chars])}\Z/
17
+ end
18
+
19
+ def call(exp)
20
+ if options[:smart_text]
21
+ super
22
+ else
23
+ exp
24
+ end
25
+ end
26
+
27
+ def on_multi(*exps)
28
+ # The [:multi] blocks serve two purposes.
29
+ # On outer level, they collect the building blocks like
30
+ # tags, verbatim text, and implicit/explicit text.
31
+ # Within a text block, they collect the individual
32
+ # lines in [:slim, :interpolate, string] blocks.
33
+ #
34
+ # Our goal here is to decide when we want to prepend and
35
+ # append newlines to those individual interpolated lines.
36
+ # We basically want the text to come out as it was originally entered,
37
+ # while removing newlines next to the enclosing tags.
38
+ #
39
+ # On outer level, we choose to prepend every time, except
40
+ # right after the opening tag or after other text block.
41
+ # We also use the append flag to recognize the last expression
42
+ # before the closing tag, as we don't want to append newline there.
43
+ #
44
+ # Within text block, we prepend only before the first line unless
45
+ # the outer level tells us not to, and we append only after the last line,
46
+ # unless the outer level tells us it is the last line before the closing tag.
47
+ # Of course, this is later subject to the special begin/end characters
48
+ # which may further suppress the newline at the corresponding line boundary.
49
+ # Also note that the lines themselves are already correctly separated by newlines,
50
+ # so we don't have to worry about that at all.
51
+ block = [:multi]
52
+ prev = nil
53
+ last_exp = exps.reject{ |exp| exp.first == :newline }.last unless @active && @append
54
+ exps.each do |exp|
55
+ @append = exp.equal?(last_exp)
56
+ if @active
57
+ @prepend = false if prev
58
+ else
59
+ @prepend = prev && ( prev.first != :slim || prev[1] != :text )
60
+ end
61
+ block << compile(exp)
62
+ prev = exp unless exp.first == :newline
63
+ end
64
+ block
65
+ end
66
+
67
+ def on_slim_text(type, content)
68
+ @active = type != :verbatim
69
+ [:slim, :text, type, compile(content)]
70
+ ensure
71
+ @active = false
72
+ end
73
+
74
+ def on_slim_text_inline(content)
75
+ # Inline text is not wrapped in multi block, so set it up as if it was.
76
+ @prepend = false
77
+ @append = true
78
+ on_slim_text(:inline, content)
79
+ end
80
+
81
+ def on_slim_interpolate(string)
82
+ if @active
83
+ string = "\n" + string if @prepend && string !~ @prepend_re
84
+ string += "\n" if @append && string !~ @append_re
85
+ end
86
+ [:slim, :interpolate, string]
87
+ end
88
+
89
+ private
90
+
91
+ def chars_re(string)
92
+ Regexp.union(string.split(//))
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,33 @@
1
+ module Slim
2
+ module Smart
3
+ class Parser < ::Slim::Parser
4
+ define_options :implicit_text => true
5
+
6
+ def initialize(opts = {})
7
+ super
8
+ word_re = options[:implicit_text] ? LC_WORD_RE : WORD_RE
9
+ attr_keys = Regexp.union(@attr_shortcut.keys.sort_by {|k| -k.size } )
10
+ @attr_shortcut_re = /\A(#{attr_keys}+)((?:#{WORD_RE}|-)*)/
11
+ tag_keys = Regexp.union((@tag_shortcut.keys - @attr_shortcut.keys).sort_by {|k| -k.size } )
12
+ @tag_re = /\A(?:#{attr_keys}(?=-*#{WORD_RE})|#{tag_keys}|\*(?=[^\s]+)|(#{word_re}(?:#{word_re}|:|-)*#{word_re}|#{word_re}+))/
13
+ end
14
+
15
+ def unknown_line_indicator
16
+ if @line =~ /\A>( ?)/
17
+ # Found explicit text block.
18
+ @stacks.last << [:slim, :text, :explicit, parse_text_block($', @indents.last + $1.size + 1)]
19
+ else
20
+ unless options[:implicit_text]
21
+ syntax_error! 'Illegal shortcut' if @line =~ @attr_shortcut_re
22
+ super
23
+ end
24
+ # Found implicit smart text block.
25
+ if line = @lines.first
26
+ indent = ( line =~ /\A\s*\Z/ ? @indents.last + 1 : get_indent(line) )
27
+ end
28
+ @stacks.last << [:slim, :text, :implicit, parse_text_block(@line, indent)]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -10,11 +10,11 @@ module Slim
10
10
  def code_attr(name, escape, value)
11
11
  if delim = @options[:merge_attrs][name]
12
12
  value = Array === value ? value.join(delim) : value.to_s
13
- attr(name, escape ? Temple::Utils.escape_html(value) : value) unless value.empty?
13
+ attr(name, escape ? Temple::Utils.escape_html_safe(value, @options[:use_html_safe]) : value) unless value.empty?
14
14
  elsif @options[:hyphen_attrs].include?(name) && Hash === value
15
15
  hyphen_attr(name, escape, value)
16
16
  elsif value != false && value != nil
17
- attr(name, value != true && escape ? Temple::Utils.escape_html(value) : value)
17
+ attr(name, value != true && escape ? Temple::Utils.escape_html_safe(value, @options[:use_html_safe]) : value)
18
18
  end
19
19
  end
20
20
 
@@ -69,7 +69,7 @@ module Slim
69
69
  hyphen_attr("#{name}-#{n.to_s.gsub('_', '-')}", escape, v)
70
70
  end
71
71
  else
72
- attr(name, value != true && escape ? Temple::Utils.escape_html(value) : value)
72
+ attr(name, value != true && escape ? Temple::Utils.escape_html_safe(value, @options[:use_html_safe]) : value)
73
73
  end
74
74
  end
75
75
  end
@@ -2,9 +2,9 @@ module Slim
2
2
  module Splat
3
3
  # @api private
4
4
  class Filter < ::Slim::Filter
5
- OPTIONS = [:merge_attrs, :attr_quote, :sort_attrs, :default_tag, :hyphen_attrs, :format]
5
+ OPTIONS = [:merge_attrs, :attr_quote, :sort_attrs, :default_tag, :hyphen_attrs, :format, :use_html_safe]
6
6
  define_options OPTIONS
7
- default_options[:hyphen_attrs] = %w(data aria)
7
+ set_default_options :hyphen_attrs => %w(data aria), :use_html_safe => ''.respond_to?(:html_safe?)
8
8
 
9
9
  def call(exp)
10
10
  @splat_options = nil
@@ -46,8 +46,8 @@ module Slim
46
46
  end
47
47
  end
48
48
 
49
- def on_slim_text(exp)
50
- [:slim, :text, @translator.call(exp)]
49
+ def on_slim_text(type, exp)
50
+ [:slim, :text, type, @translator.call(exp)]
51
51
  end
52
52
 
53
53
  private
@@ -1,5 +1,5 @@
1
1
  module Slim
2
2
  # Slim version string
3
3
  # @api public
4
- VERSION = '2.0.3'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -18,6 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = %w(lib)
20
20
 
21
- s.add_runtime_dependency('temple', ['~> 0.6.6'])
21
+ s.add_runtime_dependency('temple', ['~> 0.6.9'])
22
22
  s.add_runtime_dependency('tilt', ['>= 1.3.3', '< 2.1'])
23
23
  end
@@ -46,6 +46,8 @@ class TestSlim < MiniTest::Unit::TestCase
46
46
  raise 'Syntax error expected'
47
47
  rescue Slim::Parser::SyntaxError => ex
48
48
  assert_equal message, ex.message
49
+ message =~ /([^\s]+), Line (\d+)/
50
+ assert_backtrace ex, "#{$1}:#{$2}"
49
51
  end
50
52
 
51
53
  def assert_ruby_error(error, from, source, options = {})
@@ -58,12 +60,12 @@ class TestSlim < MiniTest::Unit::TestCase
58
60
  def assert_backtrace(ex, from)
59
61
  if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
60
62
  # HACK: Rubinius stack trace sometimes has one entry more
61
- if ex.backtrace[0] !~ /^#{Regexp.escape from}:/
62
- ex.backtrace[1] =~ /([^\s]+:\d+):/
63
+ if ex.backtrace[0] !~ /^#{Regexp.escape from}/
64
+ ex.backtrace[1] =~ /([^\s]+:\d+)/
63
65
  assert_equal from, $1
64
66
  end
65
67
  else
66
- ex.backtrace[0] =~ /([^\s]+:\d+):/
68
+ ex.backtrace[0] =~ /([^\s]+:\d+)/
67
69
  assert_equal from, $1
68
70
  end
69
71
  end
@@ -21,6 +21,17 @@ p
21
21
  assert_html '<p>Hello Ruby! Hello from within a block! Hello Ruby!</p>', source
22
22
  end
23
23
 
24
+ def test_render_variable_ending_with_do
25
+ source = %q{
26
+ - appelido=10
27
+ p= appelido
28
+ - appelido
29
+ }
30
+
31
+ assert_html '<p>10</p>', source
32
+ end
33
+
34
+
24
35
  def test_render_with_output_code_within_block
25
36
  source = %q{
26
37
  p
@@ -45,10 +45,9 @@ p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>.".html_safe
45
45
  end
46
46
  end
47
47
 
48
- # splat ignores html_safe? for now
49
48
  def test_render_splat_with_html_safe_true
50
49
  source = %q{
51
- p *{ :title => '&'.html_safe }
50
+ p *{ :title => '&amp;'.html_safe }
52
51
  }
53
52
 
54
53
  with_html_safe do
@@ -56,13 +55,24 @@ p *{ :title => '&'.html_safe }
56
55
  end
57
56
  end
58
57
 
58
+ def test_render_splat_with_html_safe_false
59
+ source = %q{
60
+ p *{ :title => '&' }
61
+ }
62
+
63
+ with_html_safe do
64
+ assert_html "<p title=\"&amp;\"></p>", source, :use_html_safe => true
65
+ end
66
+ end
67
+
68
+
59
69
  def test_render_attribute_with_html_safe_true
60
70
  source = %q{
61
- p title=('&'.html_safe)
71
+ p title=('&amp;'.html_safe)
62
72
  }
63
73
 
64
74
  with_html_safe do
65
- assert_html "<p title=\"&\"></p>", source, :use_html_safe => true
75
+ assert_html "<p title=\"&amp;\"></p>", source, :use_html_safe => true
66
76
  end
67
77
  end
68
78
 
@@ -69,6 +69,17 @@ creole:
69
69
  assert_html "<h1>head1</h1><h2>head2</h2>", source
70
70
  end
71
71
 
72
+ def test_render_with_creole_one_line
73
+ source = %q{
74
+ creole: Hello **world**,
75
+ we can write one-line embedded markup now!
76
+ = Headline
77
+ Text
78
+ .nested: creole: **Strong**
79
+ }
80
+ assert_html '<p>Hello <strong>world</strong>, we can write one-line embedded markup now!</p><h1>Headline</h1><p>Text</p><div class="nested"><p><strong>Strong</strong></p></div>', source
81
+ end
82
+
72
83
  def test_render_with_org
73
84
  # HACK: org-ruby registers itself in Tilt
74
85
  require 'org-ruby'
@@ -113,6 +124,21 @@ p Hi
113
124
  assert_html %{<script type="text/javascript">$(function() {});\n\n\nalert('hello')</script><p>Hi</p>}, source
114
125
  end
115
126
 
127
+ def test_render_with_opal
128
+ begin
129
+ # HACK: org-ruby registers itself in Tilt
130
+ require 'opal'
131
+ rescue LoadError
132
+ return
133
+ end
134
+
135
+ source = %q{
136
+ opal:
137
+ puts 'hello from opal'
138
+ }
139
+ assert_match '$puts("hello from opal")', render(source)
140
+ end
141
+
116
142
  def test_render_with_javascript_with_tabs
117
143
  source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi"
118
144
  assert_html "<script type=\"text/javascript\">$(function() {});\nalert('hello')</script><p>Hi</p>", source
@@ -14,6 +14,15 @@ html
14
14
  assert_html '<html><head><title>Simple Test Title</title></head><body><p>Hello World, meet Slim.</p></body></html>', source
15
15
  end
16
16
 
17
+ def test_relaxed_indentation_of_first_line
18
+ source = %q{
19
+ p
20
+ .content
21
+ }
22
+
23
+ assert_html "<p><div class=\"content\"></div></p>", source
24
+ end
25
+
17
26
  def test_html_tag_with_text_and_empty_line
18
27
  source = %q{
19
28
  p Hello
@@ -209,6 +218,17 @@ p#test class="paragraph" This is line one.
209
218
  assert_html "<p class=\"paragraph\" id=\"test\">This is line one.\nThis is line two.</p>", source
210
219
  end
211
220
 
221
+ def test_relaxed_text_indentation
222
+ source = %q{
223
+ p
224
+ | text block
225
+ text
226
+ line3
227
+ }
228
+
229
+ assert_html "<p>text block\ntext\n line3</p>", source
230
+ end
231
+
212
232
  def test_output_code_with_leading_spaces
213
233
  source = %q{
214
234
  p= hello_world
@@ -311,6 +331,25 @@ closed/
311
331
  assert_html '<closed />', source, :format => :xhtml
312
332
  end
313
333
 
334
+ def test_custom_attr_list_delims_option
335
+ source = %q{
336
+ p { foo="bar" x=(1+1) }
337
+ p < x=(1+1) > Hello
338
+ }
339
+
340
+ assert_html '<p foo="bar" x="2"></p><p>< x=(1+1) > Hello</p>', source
341
+ assert_html '<p foo="bar" x="2"></p><p>< x=(1+1) > Hello</p>', source, :attr_list_delims => {'{' => '}'}
342
+ assert_html '<p>{ foo="bar" x=(1+1) }</p><p x="2">Hello</p>', source, :attr_list_delims => {'<' => '>'}, :code_attr_delims => { '(' => ')' }
343
+ end
344
+
345
+ def test_closed_tag
346
+ source = %q{
347
+ closed/
348
+ }
349
+
350
+ assert_html '<closed />', source, :format => :xhtml
351
+ end
352
+
314
353
  def test_attributs_with_parens_and_spaces
315
354
  source = %q{label{ for='filter' }= hello_world}
316
355
  assert_html '<label for="filter">Hello World from @env</label>', source
@@ -19,40 +19,23 @@ doctype 5
19
19
  assert_syntax_error "Unexpected indentation\n (__TEMPLATE__), Line 3, Column 2\n div Invalid\n ^\n", source
20
20
  end
21
21
 
22
- def test_unexpected_text_indentation
22
+ def test_malformed_indentation
23
23
  source = %q{
24
24
  p
25
- | text block
26
- text
27
- }
28
-
29
- assert_syntax_error "Text line not indented deep enough.\nThe first text line defines the necessary text indentation.\n (__TEMPLATE__), Line 4, Column 3\n text\n ^\n", source
30
- end
31
-
32
- def test_unexpected_text_indentation_in_tag
33
- source = %q{
34
- ul
35
- li List1
36
- ul
37
- li a
38
- li b
39
- li List2
40
- ul
41
- li a
42
- li b
25
+ div Valid
26
+ div Invalid
43
27
  }
44
28
 
45
- assert_syntax_error "Text line not indented deep enough.\nThe first text line defines the necessary text indentation.\nAre you trying to nest a child tag in a tag containing text? Use | for the text block!\n (__TEMPLATE__), Line 4, Column 4\n ul\n ^\n", source
29
+ assert_syntax_error "Malformed indentation\n (__TEMPLATE__), Line 4, Column 1\n div Invalid\n ^\n", source
46
30
  end
47
31
 
48
- def test_malformed_indentation
32
+ def test_malformed_indentation2
49
33
  source = %q{
50
- p
51
34
  div Valid
52
35
  div Invalid
53
36
  }
54
37
 
55
- assert_syntax_error "Malformed indentation\n (__TEMPLATE__), Line 4, Column 1\n div Invalid\n ^\n", source
38
+ assert_syntax_error "Malformed indentation\n (__TEMPLATE__), Line 3, Column 1\n div Invalid\n ^\n", source
56
39
  end
57
40
 
58
41
  def test_unknown_line_indicator
@@ -76,13 +59,13 @@ p
76
59
  assert_syntax_error "Expected closing delimiter )\n (__TEMPLATE__), Line 3, Column 33\n img(src=\"img.jpg\" title={title}\n ^\n", source
77
60
  end
78
61
 
79
- def test_expected_closing_quote
62
+ def test_missing_quote_unexpected_end
80
63
  source = %q{
81
64
  p
82
65
  img(src="img.jpg
83
66
  }
84
67
 
85
- assert_syntax_error "Expected closing quote \"\n (__TEMPLATE__), Line 3, Column 18\n img(src=\"img.jpg\n ^\n", source
68
+ assert_syntax_error "Unexpected end of file\n (__TEMPLATE__), Line 3, Column 0\n \n ^\n", source
86
69
  end
87
70
 
88
71
  def test_expected_closing_attribute_delimiter