slim 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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