haml 4.0.6 → 5.2.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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +19 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +72 -0
  5. data/.yardopts +2 -3
  6. data/CHANGELOG.md +138 -4
  7. data/FAQ.md +4 -14
  8. data/Gemfile +16 -0
  9. data/MIT-LICENSE +2 -2
  10. data/README.md +79 -42
  11. data/REFERENCE.md +142 -67
  12. data/Rakefile +44 -63
  13. data/TODO +24 -0
  14. data/benchmark.rb +70 -0
  15. data/haml.gemspec +45 -0
  16. data/lib/haml.rb +2 -0
  17. data/lib/haml/.gitattributes +1 -0
  18. data/lib/haml/attribute_builder.rb +164 -0
  19. data/lib/haml/attribute_compiler.rb +235 -0
  20. data/lib/haml/attribute_parser.rb +150 -0
  21. data/lib/haml/buffer.rb +29 -136
  22. data/lib/haml/compiler.rb +110 -320
  23. data/lib/haml/engine.rb +34 -41
  24. data/lib/haml/error.rb +28 -24
  25. data/lib/haml/escapable.rb +77 -0
  26. data/lib/haml/exec.rb +38 -20
  27. data/lib/haml/filters.rb +22 -27
  28. data/lib/haml/generator.rb +42 -0
  29. data/lib/haml/helpers.rb +134 -89
  30. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  31. data/lib/haml/helpers/action_view_mods.rb +45 -60
  32. data/lib/haml/helpers/action_view_xss_mods.rb +2 -0
  33. data/lib/haml/helpers/safe_erubi_template.rb +20 -0
  34. data/lib/haml/helpers/safe_erubis_template.rb +5 -1
  35. data/lib/haml/helpers/xss_mods.rb +23 -13
  36. data/lib/haml/options.rb +63 -69
  37. data/lib/haml/parser.rb +292 -228
  38. data/lib/haml/plugin.rb +37 -0
  39. data/lib/haml/railtie.rb +38 -12
  40. data/lib/haml/sass_rails_filter.rb +18 -4
  41. data/lib/haml/template.rb +13 -6
  42. data/lib/haml/template/options.rb +13 -2
  43. data/lib/haml/temple_engine.rb +123 -0
  44. data/lib/haml/temple_line_counter.rb +30 -0
  45. data/lib/haml/util.rb +83 -202
  46. data/lib/haml/version.rb +3 -1
  47. data/yard/default/.gitignore +1 -0
  48. data/yard/default/fulldoc/html/css/common.sass +15 -0
  49. data/yard/default/layout/html/footer.erb +12 -0
  50. metadata +73 -108
  51. data/lib/haml/template/plugin.rb +0 -41
  52. data/test/engine_test.rb +0 -2013
  53. data/test/erb/_av_partial_1.erb +0 -12
  54. data/test/erb/_av_partial_2.erb +0 -8
  55. data/test/erb/action_view.erb +0 -62
  56. data/test/erb/standard.erb +0 -55
  57. data/test/filters_test.rb +0 -254
  58. data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
  59. data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
  60. data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
  61. data/test/gemfiles/Gemfile.rails-4.0.x +0 -5
  62. data/test/helper_test.rb +0 -583
  63. data/test/markaby/standard.mab +0 -52
  64. data/test/mocks/article.rb +0 -6
  65. data/test/parser_test.rb +0 -105
  66. data/test/results/content_for_layout.xhtml +0 -12
  67. data/test/results/eval_suppressed.xhtml +0 -9
  68. data/test/results/helpers.xhtml +0 -70
  69. data/test/results/helpful.xhtml +0 -10
  70. data/test/results/just_stuff.xhtml +0 -70
  71. data/test/results/list.xhtml +0 -12
  72. data/test/results/nuke_inner_whitespace.xhtml +0 -40
  73. data/test/results/nuke_outer_whitespace.xhtml +0 -148
  74. data/test/results/original_engine.xhtml +0 -20
  75. data/test/results/partial_layout.xhtml +0 -5
  76. data/test/results/partial_layout_erb.xhtml +0 -5
  77. data/test/results/partials.xhtml +0 -21
  78. data/test/results/render_layout.xhtml +0 -3
  79. data/test/results/silent_script.xhtml +0 -74
  80. data/test/results/standard.xhtml +0 -162
  81. data/test/results/tag_parsing.xhtml +0 -23
  82. data/test/results/very_basic.xhtml +0 -5
  83. data/test/results/whitespace_handling.xhtml +0 -90
  84. data/test/template_test.rb +0 -354
  85. data/test/templates/_av_partial_1.haml +0 -9
  86. data/test/templates/_av_partial_1_ugly.haml +0 -9
  87. data/test/templates/_av_partial_2.haml +0 -5
  88. data/test/templates/_av_partial_2_ugly.haml +0 -5
  89. data/test/templates/_layout.erb +0 -3
  90. data/test/templates/_layout_for_partial.haml +0 -3
  91. data/test/templates/_partial.haml +0 -8
  92. data/test/templates/_text_area.haml +0 -3
  93. data/test/templates/_text_area_helper.html.haml +0 -4
  94. data/test/templates/action_view.haml +0 -47
  95. data/test/templates/action_view_ugly.haml +0 -47
  96. data/test/templates/breakage.haml +0 -8
  97. data/test/templates/content_for_layout.haml +0 -8
  98. data/test/templates/eval_suppressed.haml +0 -11
  99. data/test/templates/helpers.haml +0 -55
  100. data/test/templates/helpful.haml +0 -11
  101. data/test/templates/just_stuff.haml +0 -85
  102. data/test/templates/list.haml +0 -12
  103. data/test/templates/nuke_inner_whitespace.haml +0 -32
  104. data/test/templates/nuke_outer_whitespace.haml +0 -144
  105. data/test/templates/original_engine.haml +0 -17
  106. data/test/templates/partial_layout.haml +0 -3
  107. data/test/templates/partial_layout_erb.erb +0 -4
  108. data/test/templates/partialize.haml +0 -1
  109. data/test/templates/partials.haml +0 -12
  110. data/test/templates/render_layout.haml +0 -2
  111. data/test/templates/silent_script.haml +0 -45
  112. data/test/templates/standard.haml +0 -43
  113. data/test/templates/standard_ugly.haml +0 -43
  114. data/test/templates/tag_parsing.haml +0 -21
  115. data/test/templates/very_basic.haml +0 -4
  116. data/test/templates/whitespace_handling.haml +0 -87
  117. data/test/test_helper.rb +0 -81
  118. data/test/util_test.rb +0 -63
data/TODO ADDED
@@ -0,0 +1,24 @@
1
+ # -*- mode: org -*-
2
+ #+STARTUP: nofold
3
+
4
+ * Documentation
5
+ Redo tutorial?
6
+ Using helpers
7
+ haml_concat and haml_tag in particular
8
+ Syntax highlighting?
9
+
10
+ * Code
11
+ Keep track of error offsets everywhere
12
+ Use this to show error location in messages
13
+ ** Haml
14
+ Support finer-grained HTML-escaping in filters
15
+ Speed
16
+ Make tags with dynamic attributes pre-render as much as possible
17
+ Including the attribute name where doable
18
+ :html improvements
19
+ Ignore closing tags where we can
20
+ http://code.google.com/speed/articles/optimizing-html.html
21
+ Requires Haml parsing refactor
22
+ Don't quote attributes that don't require it
23
+ http://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.2.2
24
+ http://www.w3.org/TR/html5/syntax.html#attributes
@@ -0,0 +1,70 @@
1
+ require "bundler/setup"
2
+ require "haml"
3
+ require "rbench"
4
+
5
+ times = (ARGV.first || 1000).to_i
6
+
7
+ if times == 0 # Invalid parameter
8
+ puts <<END
9
+ ruby #$0 [times=1000]
10
+ Benchmark Haml against various other templating languages.
11
+ END
12
+ exit 1
13
+ end
14
+
15
+ %w[erb erubi rails active_support action_controller
16
+ action_view action_pack haml/template rbench].each {|dep| require(dep)}
17
+
18
+ def view
19
+ base = ActionView::Base.new
20
+ base.view_paths << File.join(File.dirname(__FILE__), '/test')
21
+ base
22
+ end
23
+
24
+ def render(view, file)
25
+ view.render :file => file
26
+ end
27
+
28
+ RBench.run(times) do
29
+ column :haml, :title => "Haml"
30
+ column :erb, :title => "ERB"
31
+ column :erubi, :title => "Erubi"
32
+
33
+ template_name = 'standard'
34
+ haml_template = File.read("#{File.dirname(__FILE__)}/test/templates/#{template_name}.haml")
35
+ erb_template = File.read("#{File.dirname(__FILE__)}/test/erb/#{template_name}.erb")
36
+
37
+ report "Cached" do
38
+ obj = Object.new
39
+
40
+ Haml::Engine.new(haml_template).def_method(obj, :haml)
41
+ if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
42
+ obj.instance_eval("def erb; #{ERB.new(erb_template, trim_mode: '-').src}; end")
43
+ else
44
+ obj.instance_eval("def erb; #{ERB.new(erb_template, nil, '-').src}; end")
45
+ end
46
+ obj.instance_eval("def erubi; #{Erubi::Engine.new(erb_template).src}; end")
47
+
48
+ haml { obj.haml }
49
+ erb { obj.erb }
50
+ erubi { obj.erubi }
51
+ end
52
+
53
+ report "ActionView" do
54
+ # To cache the template
55
+ render view, 'templates/standard'
56
+ render view, 'erb/standard'
57
+
58
+ haml { render view, 'templates/standard' }
59
+ erubi { render view, 'erb/standard' }
60
+ end
61
+
62
+ report "ActionView with deep partials" do
63
+ # To cache the template
64
+ render view, 'templates/action_view'
65
+ render view, 'erb/action_view'
66
+
67
+ haml { render view, 'templates/action_view' }
68
+ erubi { render view, 'erb/action_view' }
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ ($LOAD_PATH << File.expand_path("../lib", __FILE__)).uniq!
2
+ require "haml/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'haml'
6
+ spec.summary = "An elegant, structured (X)HTML/XML templating engine."
7
+ spec.version = Haml::VERSION
8
+ spec.authors = ['Natalie Weizenbaum', 'Hampton Catlin', 'Norman Clarke', 'Akira Matsuda']
9
+ spec.email = ['haml@googlegroups.com', 'ronnie@dio.jp']
10
+
11
+ spec.executables = ['haml']
12
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
13
+ f.match(%r{\Atest/})
14
+ end
15
+ spec.homepage = 'http://haml.info/'
16
+ spec.license = "MIT"
17
+ spec.metadata = {
18
+ "bug_tracker_uri" => "https://github.com/haml/haml/issues",
19
+ "changelog_uri" => "https://github.com/haml/haml/blob/main/CHANGELOG.md",
20
+ "documentation_uri" => "http://haml.info/docs.html",
21
+ "homepage_uri" => "http://haml.info",
22
+ "mailing_list_uri" => "https://groups.google.com/forum/?fromgroups#!forum/haml",
23
+ "source_code_uri" => "https://github.com/haml/haml"
24
+ }
25
+
26
+ spec.required_ruby_version = '>= 2.0.0'
27
+
28
+ spec.add_dependency 'temple', '>= 0.8.0'
29
+ spec.add_dependency 'tilt'
30
+
31
+ spec.add_development_dependency 'rails', '>= 4.0.0'
32
+ spec.add_development_dependency 'rbench'
33
+ spec.add_development_dependency 'minitest', '>= 4.0'
34
+ spec.add_development_dependency 'nokogiri'
35
+ spec.add_development_dependency 'simplecov', '0.17.1' # Locked to this version due to https://github.com/codeclimate/test-reporter/issues/418
36
+
37
+ spec.description = <<-END
38
+ Haml (HTML Abstraction Markup Language) is a layer on top of HTML or XML that's
39
+ designed to express the structure of documents in a non-repetitive, elegant, and
40
+ easy way by using indentation rather than closing tags and allowing Ruby to be
41
+ embedded with ease. It was originally envisioned as a plugin for Ruby on Rails,
42
+ but it can function as a stand-alone templating engine.
43
+ END
44
+
45
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'haml/version'
2
4
 
3
5
  # The module that contains everything Haml-related:
@@ -0,0 +1 @@
1
+ version.rb merge=ours
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Haml
4
+ module AttributeBuilder
5
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
6
+ INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
7
+
8
+ class << self
9
+ def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
10
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
11
+ # some of this needs to be moved into an instance method.
12
+ join_char = hyphenate_data_attrs ? '-' : '_'
13
+
14
+ attributes.each do |key, value|
15
+ if value.is_a?(Hash)
16
+ data_attributes = attributes.delete(key)
17
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
18
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
19
+ verify_attribute_names!(data_attributes.keys)
20
+ attributes = data_attributes.merge(attributes)
21
+ end
22
+ end
23
+
24
+ result = attributes.collect do |attr, value|
25
+ next if value.nil?
26
+
27
+ value = filter_and_join(value, ' ') if attr == 'class'
28
+ value = filter_and_join(value, '_') if attr == 'id'
29
+
30
+ if value == true
31
+ next " #{attr}" if is_html
32
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
33
+ elsif value == false
34
+ next
35
+ end
36
+
37
+ value =
38
+ if escape_attrs == :once
39
+ Haml::Helpers.escape_once_without_haml_xss(value.to_s)
40
+ elsif escape_attrs
41
+ Haml::Helpers.html_escape_without_haml_xss(value.to_s)
42
+ else
43
+ value.to_s
44
+ end
45
+ " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
46
+ end
47
+ result.compact!
48
+ result.sort!
49
+ result.join
50
+ end
51
+
52
+ # @return [String, nil]
53
+ def filter_and_join(value, separator)
54
+ return '' if (value.respond_to?(:empty?) && value.empty?)
55
+
56
+ if value.is_a?(Array)
57
+ value = value.flatten
58
+ value.map! {|item| item ? item.to_s : nil}
59
+ value.compact!
60
+ value = value.join(separator)
61
+ else
62
+ value = value ? value.to_s : nil
63
+ end
64
+ !value.nil? && !value.empty? && value
65
+ end
66
+
67
+ # Merges two attribute hashes.
68
+ # This is the same as `to.merge!(from)`,
69
+ # except that it merges id, class, and data attributes.
70
+ #
71
+ # ids are concatenated with `"_"`,
72
+ # and classes are concatenated with `" "`.
73
+ # data hashes are simply merged.
74
+ #
75
+ # Destructively modifies `to`.
76
+ #
77
+ # @param to [{String => String,Hash}] The attribute hash to merge into
78
+ # @param from [{String => Object}] The attribute hash to merge from
79
+ # @return [{String => String,Hash}] `to`, after being merged
80
+ def merge_attributes!(to, from)
81
+ from.keys.each do |key|
82
+ to[key] = merge_value(key, to[key], from[key])
83
+ end
84
+ to
85
+ end
86
+
87
+ # Merge multiple values to one attribute value. No destructive operation.
88
+ #
89
+ # @param key [String]
90
+ # @param values [Array<Object>]
91
+ # @return [String,Hash]
92
+ def merge_values(key, *values)
93
+ values.inject(nil) do |to, from|
94
+ merge_value(key, to, from)
95
+ end
96
+ end
97
+
98
+ def verify_attribute_names!(attribute_names)
99
+ attribute_names.each do |attribute_name|
100
+ if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
101
+ raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Merge a couple of values to one attribute value. No destructive operation.
109
+ #
110
+ # @param to [String,Hash,nil]
111
+ # @param from [Object]
112
+ # @return [String,Hash]
113
+ def merge_value(key, to, from)
114
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
115
+ from = { nil => from } if !from.is_a?(Hash)
116
+ to = { nil => to } if !to.is_a?(Hash)
117
+ to.merge(from)
118
+ elsif key == 'id'
119
+ merged_id = filter_and_join(from, '_')
120
+ if to && merged_id
121
+ merged_id = "#{to}_#{merged_id}"
122
+ elsif to || merged_id
123
+ merged_id ||= to
124
+ end
125
+ merged_id
126
+ elsif key == 'class'
127
+ merged_class = filter_and_join(from, ' ')
128
+ if to && merged_class
129
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
130
+ elsif to || merged_class
131
+ merged_class ||= to
132
+ end
133
+ merged_class
134
+ else
135
+ from
136
+ end
137
+ end
138
+
139
+ def build_data_keys(data_hash, hyphenate, attr_name="data")
140
+ Hash[data_hash.map do |name, value|
141
+ if name == nil
142
+ [attr_name, value]
143
+ elsif hyphenate
144
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
145
+ else
146
+ ["#{attr_name}-#{name}", value]
147
+ end
148
+ end]
149
+ end
150
+
151
+ def flatten_data_attributes(data, key, join_char, seen = [])
152
+ return {key => data} unless data.is_a?(Hash)
153
+
154
+ return {key => nil} if seen.include? data.object_id
155
+ seen << data.object_id
156
+
157
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
158
+ joined = key == '' ? k : [key, k].join(join_char)
159
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml/attribute_parser'
4
+
5
+ module Haml
6
+ class AttributeCompiler
7
+ # @param type [Symbol] :static or :dynamic
8
+ # @param key [String]
9
+ # @param value [String] Actual string value for :static type, value's Ruby literal for :dynamic type.
10
+ AttributeValue = Struct.new(:type, :key, :value)
11
+
12
+ # @param options [Haml::Options]
13
+ def initialize(options)
14
+ @is_html = [:html4, :html5].include?(options[:format])
15
+ @attr_wrapper = options[:attr_wrapper]
16
+ @escape_attrs = options[:escape_attrs]
17
+ @hyphenate_data_attrs = options[:hyphenate_data_attrs]
18
+ end
19
+
20
+ # Returns Temple expression to render attributes.
21
+ #
22
+ # @param attributes [Hash]
23
+ # @param object_ref [String,:nil]
24
+ # @param dynamic_attributes [Haml::Parser::DynamicAttributes]
25
+ # @return [Array] Temple expression
26
+ def compile(attributes, object_ref, dynamic_attributes)
27
+ if object_ref != :nil || !AttributeParser.available?
28
+ return [:dynamic, compile_runtime_build(attributes, object_ref, dynamic_attributes)]
29
+ end
30
+
31
+ parsed_hashes = [dynamic_attributes.new, dynamic_attributes.old].compact.map do |attribute_hash|
32
+ unless (hash = AttributeParser.parse(attribute_hash))
33
+ return [:dynamic, compile_runtime_build(attributes, object_ref, dynamic_attributes)]
34
+ end
35
+ hash
36
+ end
37
+ attribute_values = build_attribute_values(attributes, parsed_hashes)
38
+ AttributeBuilder.verify_attribute_names!(attribute_values.map(&:key))
39
+
40
+ [:multi, *group_values_for_sort(attribute_values).map { |value_group|
41
+ compile_attribute_values(value_group)
42
+ }]
43
+ end
44
+
45
+ private
46
+
47
+ # Returns a script to render attributes on runtime.
48
+ #
49
+ # @param attributes [Hash]
50
+ # @param object_ref [String,:nil]
51
+ # @param dynamic_attributes [Haml::Parser::DynamicAttributes]
52
+ # @return [String] Attributes rendering code
53
+ def compile_runtime_build(attributes, object_ref, dynamic_attributes)
54
+ "_hamlout.attributes(#{to_literal(attributes)}, #{object_ref}, #{dynamic_attributes.to_literal})"
55
+ end
56
+
57
+ # Build array of grouped values whose sort order may go back and forth, which is also sorted with key name.
58
+ # This method needs to group values with the same start because it can be changed in `Haml::AttributeBuidler#build_data_keys`.
59
+ # @param values [Array<Haml::AttributeCompiler::AttributeValue>]
60
+ # @return [Array<Array<Haml::AttributeCompiler::AttributeValue>>]
61
+ def group_values_for_sort(values)
62
+ sorted_values = values.sort_by(&:key)
63
+ [].tap do |value_groups|
64
+ until sorted_values.empty?
65
+ key = sorted_values.first.key
66
+ value_group, sorted_values = sorted_values.partition { |v| v.key.start_with?(key) }
67
+ value_groups << value_group
68
+ end
69
+ end
70
+ end
71
+
72
+ # Returns array of AttributeValue instances from static attributes and dynamic_attributes. For each key,
73
+ # the values' order in returned value is preserved in the same order as Haml::Buffer#attributes's merge order.
74
+ #
75
+ # @param attributes [{ String => String }]
76
+ # @param parsed_hashes [{ String => String }]
77
+ # @return [Array<AttributeValue>]
78
+ def build_attribute_values(attributes, parsed_hashes)
79
+ [].tap do |attribute_values|
80
+ attributes.each do |key, static_value|
81
+ attribute_values << AttributeValue.new(:static, key, static_value)
82
+ end
83
+ parsed_hashes.each do |parsed_hash|
84
+ parsed_hash.each do |key, dynamic_value|
85
+ attribute_values << AttributeValue.new(:dynamic, key, dynamic_value)
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Compiles attribute values with the similar key to Temple expression.
92
+ #
93
+ # @param values [Array<AttributeValue>] whose `key`s are partially or fully the same from left.
94
+ # @return [Array] Temple expression
95
+ def compile_attribute_values(values)
96
+ if values.map(&:key).uniq.size == 1
97
+ compile_attribute(values.first.key, values)
98
+ else
99
+ runtime_build(values)
100
+ end
101
+ end
102
+
103
+ # @param values [Array<AttributeValue>]
104
+ # @return [Array] Temple expression
105
+ def runtime_build(values)
106
+ hash_content = values.group_by(&:key).map do |key, values_for_key|
107
+ "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
108
+ end.join(', ')
109
+ [:dynamic, "_hamlout.attributes({ #{hash_content} }, nil)"]
110
+ end
111
+
112
+ # Renders attribute values statically.
113
+ #
114
+ # @param values [Array<AttributeValue>]
115
+ # @return [Array] Temple expression
116
+ def static_build(values)
117
+ hash_content = values.group_by(&:key).map do |key, values_for_key|
118
+ "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
119
+ end.join(', ')
120
+
121
+ arguments = [@is_html, @attr_wrapper, @escape_attrs, @hyphenate_data_attrs]
122
+ code = "::Haml::AttributeBuilder.build_attributes"\
123
+ "(#{arguments.map(&method(:to_literal)).join(', ')}, { #{hash_content} })"
124
+ [:static, eval(code).to_s]
125
+ end
126
+
127
+ # @param key [String]
128
+ # @param values [Array<AttributeValue>]
129
+ # @return [String]
130
+ def merged_value(key, values)
131
+ if values.size == 1
132
+ attr_literal(values.first)
133
+ else
134
+ "::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&method(:attr_literal)).join(', ')})"
135
+ end
136
+ end
137
+
138
+ # @param str [String]
139
+ # @return [String]
140
+ def frozen_string(str)
141
+ "#{to_literal(str)}.freeze"
142
+ end
143
+
144
+ # Compiles attribute values for one key to Temple expression that generates ` key='value'`.
145
+ #
146
+ # @param key [String]
147
+ # @param values [Array<AttributeValue>]
148
+ # @return [Array] Temple expression
149
+ def compile_attribute(key, values)
150
+ if values.all? { |v| Temple::StaticAnalyzer.static?(attr_literal(v)) }
151
+ return static_build(values)
152
+ end
153
+
154
+ case key
155
+ when 'id', 'class'
156
+ compile_id_or_class_attribute(key, values)
157
+ else
158
+ compile_common_attribute(key, values)
159
+ end
160
+ end
161
+
162
+ # @param id_or_class [String] "id" or "class"
163
+ # @param values [Array<AttributeValue>]
164
+ # @return [Array] Temple expression
165
+ def compile_id_or_class_attribute(id_or_class, values)
166
+ var = unique_name
167
+ [:multi,
168
+ [:code, "#{var} = (#{merged_value(id_or_class, values)})"],
169
+ [:case, var,
170
+ ['Hash, Array', runtime_build([AttributeValue.new(:dynamic, id_or_class, var)])],
171
+ ['false, nil', [:multi]],
172
+ [:else, [:multi,
173
+ [:static, " #{id_or_class}=#{@attr_wrapper}"],
174
+ [:escape, Escapable::EscapeSafeBuffer.new(@escape_attrs), [:dynamic, var]],
175
+ [:static, @attr_wrapper]],
176
+ ]
177
+ ],
178
+ ]
179
+ end
180
+
181
+ # @param key [String] Not "id" or "class"
182
+ # @param values [Array<AttributeValue>]
183
+ # @return [Array] Temple expression
184
+ def compile_common_attribute(key, values)
185
+ var = unique_name
186
+ [:multi,
187
+ [:code, "#{var} = (#{merged_value(key, values)})"],
188
+ [:case, var,
189
+ ['Hash', runtime_build([AttributeValue.new(:dynamic, key, var)])],
190
+ ['true', true_value(key)],
191
+ ['false, nil', [:multi]],
192
+ [:else, [:multi,
193
+ [:static, " #{key}=#{@attr_wrapper}"],
194
+ [:escape, Escapable::EscapeSafeBuffer.new(@escape_attrs), [:dynamic, var]],
195
+ [:static, @attr_wrapper]],
196
+ ]
197
+ ],
198
+ ]
199
+ end
200
+
201
+ def true_value(key)
202
+ if @is_html
203
+ [:static, " #{key}"]
204
+ else
205
+ [:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
206
+ end
207
+ end
208
+
209
+ def unique_name
210
+ @unique_name ||= 0
211
+ "_haml_attribute_compiler#{@unique_name += 1}"
212
+ end
213
+
214
+ # @param [Haml::AttributeCompiler::AttributeValue] attr
215
+ def attr_literal(attr)
216
+ case attr.type
217
+ when :static
218
+ to_literal(attr.value)
219
+ when :dynamic
220
+ attr.value
221
+ end
222
+ end
223
+
224
+ # For haml/haml#972
225
+ # @param [Object] value
226
+ def to_literal(value)
227
+ case value
228
+ when true, false
229
+ value.to_s
230
+ else
231
+ Haml::Util.inspect_obj(value)
232
+ end
233
+ end
234
+ end
235
+ end