temple 0.6.7 → 0.10.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 (79) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +34 -0
  3. data/.gitignore +1 -0
  4. data/CHANGES +106 -1
  5. data/EXPRESSIONS.md +3 -2
  6. data/Gemfile +0 -1
  7. data/README.md +14 -10
  8. data/Rakefile +4 -11
  9. data/lib/temple/engine.rb +7 -3
  10. data/lib/temple/erb/engine.rb +5 -3
  11. data/lib/temple/erb/parser.rb +1 -1
  12. data/lib/temple/erb/trimming.rb +11 -26
  13. data/lib/temple/filters/ambles.rb +21 -0
  14. data/lib/temple/filters/encoding.rb +1 -1
  15. data/lib/temple/filters/eraser.rb +1 -1
  16. data/lib/temple/filters/escapable.rb +2 -2
  17. data/lib/temple/filters/remove_bom.rb +2 -9
  18. data/lib/temple/filters/static_analyzer.rb +30 -0
  19. data/lib/temple/filters/string_splitter.rb +141 -0
  20. data/lib/temple/filters/validator.rb +1 -1
  21. data/lib/temple/generator.rb +32 -6
  22. data/lib/temple/generators/array.rb +2 -2
  23. data/lib/temple/generators/array_buffer.rb +6 -5
  24. data/lib/temple/generators/erb.rb +1 -5
  25. data/lib/temple/generators/rails_output_buffer.rb +7 -8
  26. data/lib/temple/generators/string_buffer.rb +2 -2
  27. data/lib/temple/html/attribute_merger.rb +6 -11
  28. data/lib/temple/html/attribute_remover.rb +1 -1
  29. data/lib/temple/html/attribute_sorter.rb +1 -1
  30. data/lib/temple/html/fast.rb +49 -44
  31. data/lib/temple/html/pretty.rb +34 -43
  32. data/lib/temple/html/safe.rb +23 -0
  33. data/lib/temple/map.rb +105 -0
  34. data/lib/temple/mixins/dispatcher.rb +10 -7
  35. data/lib/temple/mixins/engine_dsl.rb +42 -67
  36. data/lib/temple/mixins/grammar_dsl.rb +10 -8
  37. data/lib/temple/mixins/options.rb +26 -24
  38. data/lib/temple/mixins/template.rb +3 -3
  39. data/lib/temple/static_analyzer.rb +77 -0
  40. data/lib/temple/templates/rails.rb +17 -36
  41. data/lib/temple/templates/tilt.rb +7 -13
  42. data/lib/temple/utils.rb +27 -29
  43. data/lib/temple/version.rb +1 -1
  44. data/lib/temple.rb +8 -4
  45. data/spec/engine_spec.rb +189 -0
  46. data/{test/test_erb.rb → spec/erb_spec.rb} +12 -13
  47. data/spec/filter_spec.rb +29 -0
  48. data/{test/filters/test_code_merger.rb → spec/filters/code_merger_spec.rb} +7 -7
  49. data/{test/filters/test_control_flow.rb → spec/filters/control_flow_spec.rb} +13 -13
  50. data/{test/filters/test_dynamic_inliner.rb → spec/filters/dynamic_inliner_spec.rb} +18 -18
  51. data/{test/filters/test_eraser.rb → spec/filters/eraser_spec.rb} +13 -13
  52. data/{test/filters/test_escapable.rb → spec/filters/escapable_spec.rb} +15 -13
  53. data/{test/filters/test_multi_flattener.rb → spec/filters/multi_flattener_spec.rb} +4 -4
  54. data/spec/filters/static_analyzer_spec.rb +35 -0
  55. data/{test/filters/test_static_merger.rb → spec/filters/static_merger_spec.rb} +7 -7
  56. data/spec/filters/string_splitter_spec.rb +50 -0
  57. data/spec/generator_spec.rb +158 -0
  58. data/spec/grammar_spec.rb +47 -0
  59. data/{test/html/test_attribute_merger.rb → spec/html/attribute_merger_spec.rb} +11 -11
  60. data/{test/html/test_attribute_remover.rb → spec/html/attribute_remover_spec.rb} +7 -7
  61. data/{test/html/test_attribute_sorter.rb → spec/html/attribute_sorter_spec.rb} +8 -8
  62. data/{test/html/test_fast.rb → spec/html/fast_spec.rb} +23 -23
  63. data/{test/html/test_pretty.rb → spec/html/pretty_spec.rb} +9 -15
  64. data/spec/map_spec.rb +39 -0
  65. data/{test/mixins/test_dispatcher.rb → spec/mixins/dispatcher_spec.rb} +12 -12
  66. data/{test/mixins/test_grammar_dsl.rb → spec/mixins/grammar_dsl_spec.rb} +19 -19
  67. data/{test/helper.rb → spec/spec_helper.rb} +9 -15
  68. data/spec/static_analyzer_spec.rb +39 -0
  69. data/spec/utils_spec.rb +39 -0
  70. data/temple.gemspec +4 -2
  71. metadata +62 -63
  72. data/.travis.yml +0 -13
  73. data/lib/temple/hash.rb +0 -104
  74. data/test/test_engine.rb +0 -170
  75. data/test/test_filter.rb +0 -29
  76. data/test/test_generator.rb +0 -136
  77. data/test/test_grammar.rb +0 -47
  78. data/test/test_hash.rb +0 -39
  79. data/test/test_utils.rb +0 -39
@@ -5,14 +5,40 @@ module Temple
5
5
  #
6
6
  # @api public
7
7
  class Generator
8
+ include Utils
8
9
  include Mixins::CompiledDispatcher
9
10
  include Mixins::Options
10
11
 
11
- define_options :capture_generator => 'StringBuffer',
12
- :buffer => '_buf'
12
+ define_options :save_buffer,
13
+ capture_generator: 'StringBuffer',
14
+ buffer: '_buf',
15
+ freeze_static: true
13
16
 
14
17
  def call(exp)
15
- [preamble, compile(exp), postamble].join('; ')
18
+ [preamble, compile(exp), postamble].flatten.compact.join('; ')
19
+ end
20
+
21
+ def preamble
22
+ [save_buffer, create_buffer]
23
+ end
24
+
25
+ def postamble
26
+ [return_buffer, restore_buffer]
27
+ end
28
+
29
+ def save_buffer
30
+ "begin; #{@original_buffer = unique_name} = #{buffer} if defined?(#{buffer})" if options[:save_buffer]
31
+ end
32
+
33
+ def restore_buffer
34
+ "ensure; #{buffer} = #{@original_buffer}; end" if options[:save_buffer]
35
+ end
36
+
37
+ def create_buffer
38
+ end
39
+
40
+ def return_buffer
41
+ 'nil'
16
42
  end
17
43
 
18
44
  def on(*exp)
@@ -20,7 +46,7 @@ module Temple
20
46
  end
21
47
 
22
48
  def on_multi(*exp)
23
- exp.map {|e| compile(e) }.join('; ')
49
+ exp.map {|e| compile(e) }.join('; '.freeze)
24
50
  end
25
51
 
26
52
  def on_newline
@@ -28,11 +54,11 @@ module Temple
28
54
  end
29
55
 
30
56
  def on_capture(name, exp)
31
- capture_generator.new(:buffer => name).call(exp)
57
+ capture_generator.new(buffer: name).call(exp)
32
58
  end
33
59
 
34
60
  def on_static(text)
35
- concat(text.inspect)
61
+ concat(options[:freeze_static] ? "#{text.inspect}.freeze" : text.inspect)
36
62
  end
37
63
 
38
64
  def on_dynamic(code)
@@ -9,11 +9,11 @@ module Temple
9
9
  #
10
10
  # @api public
11
11
  class Array < Generator
12
- def preamble
12
+ def create_buffer
13
13
  "#{buffer} = []"
14
14
  end
15
15
 
16
- def postamble
16
+ def return_buffer
17
17
  buffer
18
18
  end
19
19
  end
@@ -5,23 +5,24 @@ module Temple
5
5
  # _buf = []
6
6
  # _buf << "static"
7
7
  # _buf << dynamic
8
- # _buf.join
8
+ # _buf.join("")
9
9
  #
10
10
  # @api public
11
11
  class ArrayBuffer < Array
12
12
  def call(exp)
13
13
  case exp.first
14
14
  when :static
15
- "#{buffer} = #{exp.last.inspect}"
15
+ [save_buffer, "#{buffer} = #{exp.last.inspect}", restore_buffer].compact.join('; ')
16
16
  when :dynamic
17
- "#{buffer} = (#{exp.last}).to_s"
17
+ [save_buffer, "#{buffer} = (#{exp.last}).to_s", restore_buffer].compact.join('; ')
18
18
  else
19
19
  super
20
20
  end
21
21
  end
22
22
 
23
- def postamble
24
- "#{buffer} = #{buffer}.join"
23
+ def return_buffer
24
+ freeze = options[:freeze_static] ? '.freeze' : ''
25
+ "#{buffer} = #{buffer}.join(\"\"#{freeze})"
25
26
  end
26
27
  end
27
28
  end
@@ -9,7 +9,7 @@ module Temple
9
9
  end
10
10
 
11
11
  def on_multi(*exp)
12
- exp.map {|e| compile(e) }.join
12
+ exp.map {|e| compile(e) }.join('')
13
13
  end
14
14
 
15
15
  def on_capture(name, exp)
@@ -20,10 +20,6 @@ module Temple
20
20
  text
21
21
  end
22
22
 
23
- def on_newline
24
- "<%\n%>"
25
- end
26
-
27
23
  def on_dynamic(code)
28
24
  "<%= #{code} %>"
29
25
  end
@@ -9,18 +9,17 @@ module Temple
9
9
  #
10
10
  # @api public
11
11
  class RailsOutputBuffer < StringBuffer
12
- define_options :streaming,
13
- :buffer_class => 'ActiveSupport::SafeBuffer',
14
- :buffer => '@output_buffer',
15
- # output_buffer is needed for Rails 3.1 Streaming support
16
- :capture_generator => RailsOutputBuffer
12
+ define_options :streaming, # ignored
13
+ buffer_class: 'ActionView::OutputBuffer',
14
+ buffer: '@output_buffer',
15
+ capture_generator: RailsOutputBuffer
17
16
 
18
17
  def call(exp)
19
- [preamble, compile(exp), postamble].join('; ')
18
+ [preamble, compile(exp), postamble].flatten.compact.join('; '.freeze)
20
19
  end
21
20
 
22
- def preamble
23
- if options[:streaming] && options[:buffer] == '@output_buffer'
21
+ def create_buffer
22
+ if buffer == '@output_buffer'
24
23
  "#{buffer} = output_buffer || #{options[:buffer_class]}.new"
25
24
  else
26
25
  "#{buffer} = #{options[:buffer_class]}.new"
@@ -9,11 +9,11 @@ module Temple
9
9
  #
10
10
  # @api public
11
11
  class StringBuffer < ArrayBuffer
12
- def preamble
12
+ def create_buffer
13
13
  "#{buffer} = ''"
14
14
  end
15
15
 
16
- def postamble
16
+ def return_buffer
17
17
  buffer
18
18
  end
19
19
 
@@ -3,43 +3,38 @@ module Temple
3
3
  # This filter merges html attributes (e.g. used for id and class)
4
4
  # @api public
5
5
  class AttributeMerger < Filter
6
- define_options :merge_attrs => {'id' => '_', 'class' => ' '}
6
+ define_options merge_attrs: {'id' => '_', 'class' => ' '}
7
7
 
8
8
  def on_html_attrs(*attrs)
9
- names = []
10
9
  values = {}
11
10
 
12
- attrs.each do |attr|
13
- name, value = attr[2].to_s, attr[3]
11
+ attrs.each do |_, _, name, value|
12
+ name = name.to_s
14
13
  if values[name]
15
14
  raise(FilterError, "Multiple #{name} attributes specified") unless options[:merge_attrs][name]
16
15
  values[name] << value
17
16
  else
18
17
  values[name] = [value]
19
- names << name
20
18
  end
21
19
  end
22
20
 
23
- attrs = names.map do |name|
24
- value = values[name]
21
+ attrs = values.map do |name, value|
25
22
  if (delimiter = options[:merge_attrs][name]) && value.size > 1
26
23
  exp = [:multi]
27
24
  if value.all? {|v| contains_nonempty_static?(v) }
28
25
  exp << value.first
29
26
  value[1..-1].each {|v| exp << [:static, delimiter] << v }
30
- [:html, :attr, name, exp]
31
27
  else
32
28
  captures = unique_name
33
29
  exp << [:code, "#{captures} = []"]
34
30
  value.each_with_index {|v, i| exp << [:capture, "#{captures}[#{i}]", v] }
35
31
  exp << [:dynamic, "#{captures}.reject(&:empty?).join(#{delimiter.inspect})"]
36
32
  end
37
- [:html, :attr, name, exp]
38
33
  else
39
- [:html, :attr, name, value.first]
34
+ exp = value.first
40
35
  end
36
+ [:html, :attr, name, exp]
41
37
  end
42
-
43
38
  [:html, :attrs, *attrs]
44
39
  end
45
40
  end
@@ -3,7 +3,7 @@ module Temple
3
3
  # This filter removes empty attributes
4
4
  # @api public
5
5
  class AttributeRemover < Filter
6
- define_options :remove_empty_attrs => %w(id class)
6
+ define_options remove_empty_attrs: %w(id class)
7
7
 
8
8
  def initialize(opts = {})
9
9
  super
@@ -3,7 +3,7 @@ module Temple
3
3
  # This filter sorts html attributes.
4
4
  # @api public
5
5
  class AttributeSorter < Filter
6
- define_options :sort_attrs => true
6
+ define_options sort_attrs: true
7
7
 
8
8
  def call(exp)
9
9
  options[:sort_attrs] ? super : exp
@@ -2,39 +2,50 @@ module Temple
2
2
  module HTML
3
3
  # @api public
4
4
  class Fast < Filter
5
- XHTML_DOCTYPES = {
6
- '1.1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
7
- '5' => '<!DOCTYPE html>',
8
- 'html' => '<!DOCTYPE html>',
9
- 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
10
- 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
11
- 'mobile' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
12
- 'basic' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
13
- 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
14
- }.freeze
5
+ DOCTYPES = {
6
+ xml: {
7
+ '1.1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
8
+ '5' => '<!DOCTYPE html>',
9
+ 'html' => '<!DOCTYPE html>',
10
+ 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
11
+ 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
12
+ 'mobile' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
13
+ 'basic' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
14
+ 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
15
+ 'svg' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
16
+ },
17
+ html: {
18
+ '5' => '<!DOCTYPE html>',
19
+ 'html' => '<!DOCTYPE html>',
20
+ 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
21
+ 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
22
+ 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
23
+ }
24
+ }
25
+ DOCTYPES[:xhtml] = DOCTYPES[:xml]
26
+ DOCTYPES.freeze
15
27
 
16
- HTML_DOCTYPES = {
17
- '5' => '<!DOCTYPE html>',
18
- 'html' => '<!DOCTYPE html>',
19
- 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
20
- 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
21
- 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
22
- }.freeze
28
+ # See http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements
29
+ HTML_VOID_ELEMENTS = %w[area base br col embed hr img input keygen link menuitem meta param source track wbr]
23
30
 
24
- define_options :format => :xhtml,
25
- :attr_quote => '"',
26
- :autoclose => %w[meta img link br hr input area param col base],
27
- :js_wrapper => nil
28
-
29
- HTML = [:html, :html4, :html5]
31
+ define_options format: :xhtml,
32
+ attr_quote: '"',
33
+ autoclose: HTML_VOID_ELEMENTS,
34
+ js_wrapper: nil
30
35
 
31
36
  def initialize(opts = {})
32
37
  super
33
- unless [:xhtml, *HTML].include?(options[:format])
34
- raise ArgumentError, "Invalid format #{options[:format].inspect}"
38
+ @format = options[:format]
39
+ unless [:xhtml, :html, :xml].include?(@format)
40
+ if @format == :html4 || @format == :html5
41
+ warn "Format #{@format.inspect} is deprecated, use :html"
42
+ @format = :html
43
+ else
44
+ raise ArgumentError, "Invalid format #{@format.inspect}"
45
+ end
35
46
  end
36
47
  wrapper = options[:js_wrapper]
37
- wrapper = xhtml? ? :cdata : :comment if wrapper == :guess
48
+ wrapper = @format == :xml || @format == :xhtml ? :cdata : :comment if wrapper == :guess
38
49
  @js_wrapper =
39
50
  case wrapper
40
51
  when :comment
@@ -51,25 +62,15 @@ module Temple
51
62
  end
52
63
  end
53
64
 
54
- def xhtml?
55
- options[:format] == :xhtml
56
- end
57
-
58
- def html?
59
- HTML.include?(options[:format])
60
- end
61
-
62
65
  def on_html_doctype(type)
63
66
  type = type.to_s.downcase
64
67
 
65
68
  if type =~ /^xml(\s+(.+))?$/
66
- raise(FilterError, 'Invalid xml directive in html mode') if html?
69
+ raise(FilterError, 'Invalid xml directive in html mode') if @format == :html
67
70
  w = options[:attr_quote]
68
71
  str = "<?xml version=#{w}1.0#{w} encoding=#{w}#{$2 || 'utf-8'}#{w} ?>"
69
- elsif html?
70
- str = HTML_DOCTYPES[type] || raise(FilterError, "Invalid html doctype #{type}")
71
72
  else
72
- str = XHTML_DOCTYPES[type] || raise(FilterError, "Invalid xhtml doctype #{type}")
73
+ str = DOCTYPES[@format][type] || raise(FilterError, "Invalid doctype #{type}")
73
74
  end
74
75
 
75
76
  [:static, str]
@@ -91,9 +92,9 @@ module Temple
91
92
 
92
93
  def on_html_tag(name, attrs, content = nil)
93
94
  name = name.to_s
94
- closed = !content || (empty_exp?(content) && options[:autoclose].include?(name))
95
+ closed = !content || (empty_exp?(content) && (@format == :xml || options[:autoclose].include?(name)))
95
96
  result = [:multi, [:static, "<#{name}"], compile(attrs)]
96
- result << [:static, (closed && xhtml? ? ' /' : '') + '>']
97
+ result << [:static, (closed && @format != :html ? ' /' : '') + '>']
97
98
  result << compile(content) if content
98
99
  result << [:static, "</#{name}>"] if !closed
99
100
  result
@@ -104,10 +105,14 @@ module Temple
104
105
  end
105
106
 
106
107
  def on_html_attr(name, value)
107
- [:multi,
108
- [:static, " #{name}=#{options[:attr_quote]}"],
109
- compile(value),
110
- [:static, options[:attr_quote]]]
108
+ if @format == :html && empty_exp?(value)
109
+ [:static, " #{name}"]
110
+ else
111
+ [:multi,
112
+ [:static, " #{name}=#{options[:attr_quote]}"],
113
+ compile(value),
114
+ [:static, options[:attr_quote]]]
115
+ end
111
116
  end
112
117
 
113
118
  def on_html_js(content)
@@ -2,21 +2,21 @@ module Temple
2
2
  module HTML
3
3
  # @api public
4
4
  class Pretty < Fast
5
- define_options :indent => ' ',
6
- :pretty => true,
7
- :indent_tags => %w(article aside audio base body datalist dd div dl dt
8
- fieldset figure footer form head h1 h2 h3 h4 h5 h6
9
- header hgroup hr html li link meta nav ol option p
10
- rp rt ruby section script style table tbody td tfoot
11
- th thead tr ul video doctype).freeze,
12
- :pre_tags => %w(code pre textarea).freeze
5
+ define_options indent: ' ',
6
+ pretty: true,
7
+ indent_tags: %w(article aside audio base body datalist dd div dl dt
8
+ fieldset figure footer form head h1 h2 h3 h4 h5 h6
9
+ header hgroup hr html li link meta nav ol option p
10
+ rp rt ruby section script style table tbody td tfoot
11
+ th thead tr ul video doctype).freeze,
12
+ pre_tags: %w(code pre textarea).freeze
13
13
 
14
14
  def initialize(opts = {})
15
15
  super
16
- @last = nil
16
+ @indent_next = nil
17
17
  @indent = 0
18
18
  @pretty = options[:pretty]
19
- @pre_tags = Regexp.new(options[:pre_tags].map {|t| "<#{t}" }.join('|'))
19
+ @pre_tags = @format != :xml && Regexp.union(options[:pre_tags].map {|t| "<#{t}" })
20
20
  end
21
21
 
22
22
  def call(exp)
@@ -24,36 +24,19 @@ module Temple
24
24
  end
25
25
 
26
26
  def on_static(content)
27
- if @pretty
28
- if @pre_tags !~ content
29
- content = content.sub(/\A\s*\n?/, "\n") if options[:indent_tags].include?(@last)
30
- content = content.gsub("\n", indent)
31
- end
32
- @last = :static
27
+ return [:static, content] unless @pretty
28
+ unless @pre_tags && @pre_tags =~ content
29
+ content = content.sub(/\A\s*\n?/, "\n".freeze) if @indent_next
30
+ content = content.gsub("\n".freeze, indent)
33
31
  end
32
+ @indent_next = false
34
33
  [:static, content]
35
34
  end
36
35
 
37
36
  def on_dynamic(code)
38
- if @pretty
39
- tmp = unique_name
40
- indent_code = ''
41
- indent_code << "#{tmp} = #{tmp}.sub(/\\A\\s*\\n?/, \"\\n\"); " if options[:indent_tags].include?(@last)
42
- indent_code << "#{tmp} = #{tmp}.gsub(\"\n\", #{indent.inspect}); "
43
- if ''.respond_to?(:html_safe)
44
- safe = unique_name
45
- # we have to first save if the string was html_safe
46
- # otherwise the gsub operation will lose that knowledge
47
- indent_code = "#{safe} = #{tmp}.html_safe?; #{indent_code}#{tmp} = #{tmp}.html_safe if #{safe}; "
48
- end
49
- @last = :dynamic
50
- [:multi,
51
- [:code, "#{tmp} = (#{code}).to_s"],
52
- [:code, "if #{@pre_tags_name} !~ #{tmp}; #{indent_code}end"],
53
- [:dynamic, tmp]]
54
- else
55
- [:dynamic, code]
56
- end
37
+ return [:dynamic, code] unless @pretty
38
+ indent_next, @indent_next = @indent_next, false
39
+ [:dynamic, "::Temple::Utils.indent_dynamic((#{code}), #{indent_next.inspect}, #{indent.inspect}#{@pre_tags ? ', ' + @pre_tags_name : ''})"]
57
40
  end
58
41
 
59
42
  def on_html_doctype(type)
@@ -64,7 +47,7 @@ module Temple
64
47
  def on_html_comment(content)
65
48
  return super unless @pretty
66
49
  result = [:multi, [:static, tag_indent('comment')], super]
67
- @last = :comment
50
+ @indent_next = false
68
51
  result
69
52
  end
70
53
 
@@ -76,16 +59,18 @@ module Temple
76
59
 
77
60
  @pretty = false
78
61
  result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)]
79
- result << [:static, (closed && xhtml? ? ' /' : '') + '>']
62
+ result << [:static, (closed && @format != :html ? ' /' : '') + '>']
80
63
 
81
- @pretty = !options[:pre_tags].include?(name)
64
+ @pretty = !@pre_tags || !options[:pre_tags].include?(name)
82
65
  if content
83
66
  @indent += 1
84
67
  result << compile(content)
85
68
  @indent -= 1
86
69
  end
87
- result << [:static, "#{content && !empty_exp?(content) ? tag_indent(name) : ''}</#{name}>"] unless closed
88
-
70
+ unless closed
71
+ indent = tag_indent(name)
72
+ result << [:static, "#{content && !empty_exp?(content) ? indent : ''}</#{name}>"]
73
+ end
89
74
  @pretty = true
90
75
  result
91
76
  end
@@ -93,6 +78,7 @@ module Temple
93
78
  protected
94
79
 
95
80
  def preamble
81
+ return [:multi] unless @pre_tags
96
82
  @pre_tags_name = unique_name
97
83
  [:code, "#{@pre_tags_name} = /#{@pre_tags.source}/"]
98
84
  end
@@ -103,9 +89,14 @@ module Temple
103
89
 
104
90
  # Return indentation before tag
105
91
  def tag_indent(name)
106
- result = @last && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
107
- @last = name
108
- result
92
+ if @format == :xml
93
+ flag = @indent_next != nil
94
+ @indent_next = true
95
+ else
96
+ flag = @indent_next != nil && (@indent_next || options[:indent_tags].include?(name))
97
+ @indent_next = options[:indent_tags].include?(name)
98
+ end
99
+ flag ? indent : ''
109
100
  end
110
101
  end
111
102
  end
@@ -0,0 +1,23 @@
1
+ module Temple
2
+ module HTML
3
+ class SafeString < String
4
+ def html_safe?; true end
5
+ def html_safe; self end
6
+ def to_s; self end
7
+ end
8
+ end
9
+ end
10
+
11
+ class Object
12
+ def html_safe?; false end
13
+ end
14
+
15
+ class Numeric
16
+ def html_safe?; true end
17
+ end
18
+
19
+ class String
20
+ def html_safe
21
+ Temple::HTML::SafeString.new(self)
22
+ end
23
+ end
data/lib/temple/map.rb ADDED
@@ -0,0 +1,105 @@
1
+ module Temple
2
+ # Immutable map class which supports map merging
3
+ # @api public
4
+ class ImmutableMap
5
+ include Enumerable
6
+
7
+ def initialize(*map)
8
+ @map = map.compact
9
+ end
10
+
11
+ def include?(key)
12
+ @map.any? {|h| h.include?(key) }
13
+ end
14
+
15
+ def [](key)
16
+ @map.each {|h| return h[key] if h.include?(key) }
17
+ nil
18
+ end
19
+
20
+ def each
21
+ keys.each {|k| yield(k, self[k]) }
22
+ end
23
+
24
+ def keys
25
+ @map.inject([]) {|keys, h| keys.concat(h.keys) }.uniq
26
+ end
27
+
28
+ def values
29
+ keys.map {|k| self[k] }
30
+ end
31
+
32
+ def to_hash
33
+ result = {}
34
+ each {|k, v| result[k] = v }
35
+ result
36
+ end
37
+ end
38
+
39
+ # Mutable map class which supports map merging
40
+ # @api public
41
+ class MutableMap < ImmutableMap
42
+ def initialize(*map)
43
+ super({}, *map)
44
+ end
45
+
46
+ def []=(key, value)
47
+ @map.first[key] = value
48
+ end
49
+
50
+ def update(map)
51
+ @map.first.update(map)
52
+ end
53
+ end
54
+
55
+ class OptionMap < MutableMap
56
+ def initialize(*map, &block)
57
+ super(*map)
58
+ @handler = block
59
+ @valid = {}
60
+ @deprecated = {}
61
+ end
62
+
63
+ def []=(key, value)
64
+ validate_key!(key)
65
+ super
66
+ end
67
+
68
+ def update(map)
69
+ validate_map!(map)
70
+ super
71
+ end
72
+
73
+ def valid_keys
74
+ (keys + @valid.keys +
75
+ @map.map {|h| h.valid_keys if h.respond_to?(:valid_keys) }.compact.flatten).uniq
76
+ end
77
+
78
+ def add_valid_keys(*keys)
79
+ keys.flatten.each { |key| @valid[key] = true }
80
+ end
81
+
82
+ def add_deprecated_keys(*keys)
83
+ keys.flatten.each { |key| @valid[key] = @deprecated[key] = true }
84
+ end
85
+
86
+ def validate_map!(map)
87
+ map.to_hash.keys.each {|key| validate_key!(key) }
88
+ end
89
+
90
+ def validate_key!(key)
91
+ @handler.call(self, key, :deprecated) if deprecated_key?(key)
92
+ @handler.call(self, key, :invalid) unless valid_key?(key)
93
+ end
94
+
95
+ def deprecated_key?(key)
96
+ @deprecated.include?(key) ||
97
+ @map.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) }
98
+ end
99
+
100
+ def valid_key?(key)
101
+ include?(key) || @valid.include?(key) ||
102
+ @map.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) }
103
+ end
104
+ end
105
+ end