mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
215 lines
7.3 KiB
Ruby
215 lines
7.3 KiB
Ruby
# frozen_string_literal: true
|
|
require 'haml/attribute_parser'
|
|
|
|
module Haml
|
|
class AttributeCompiler
|
|
# @param type [Symbol] :static or :dynamic
|
|
# @param key [String]
|
|
# @param value [String] Actual string value for :static type, value's Ruby literal for :dynamic type.
|
|
class AttributeValue < Struct.new(:type, :key, :value)
|
|
# @return [String] A Ruby literal of value.
|
|
def to_literal
|
|
case type
|
|
when :static
|
|
Haml::Util.inspect_obj(value)
|
|
when :dynamic
|
|
value
|
|
end
|
|
end
|
|
|
|
# Key's substring before a hyphen. This is necessary because values with the same
|
|
# base_key can conflict by Haml::AttributeBuidler#build_data_keys.
|
|
def base_key
|
|
key.split('-', 2).first
|
|
end
|
|
end
|
|
|
|
# Returns a script to render attributes on runtime.
|
|
#
|
|
# @param attributes [Hash]
|
|
# @param object_ref [String,:nil]
|
|
# @param dynamic_attributes [DynamicAttributes]
|
|
# @return [String] Attributes rendering code
|
|
def self.runtime_build(attributes, object_ref, dynamic_attributes)
|
|
"_hamlout.attributes(#{Haml::Util.inspect_obj(attributes)}, #{object_ref},#{dynamic_attributes.to_literal})"
|
|
end
|
|
|
|
# @param options [Haml::Options]
|
|
def initialize(options)
|
|
@is_html = [:html4, :html5].include?(options[:format])
|
|
@attr_wrapper = options[:attr_wrapper]
|
|
@escape_attrs = options[:escape_attrs]
|
|
@hyphenate_data_attrs = options[:hyphenate_data_attrs]
|
|
end
|
|
|
|
# Returns Temple expression to render attributes.
|
|
#
|
|
# @param attributes [Hash]
|
|
# @param object_ref [String,:nil]
|
|
# @param dynamic_attributes [DynamicAttributes]
|
|
# @return [Array] Temple expression
|
|
def compile(attributes, object_ref, dynamic_attributes)
|
|
if object_ref != :nil || !AttributeParser.available?
|
|
return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
|
|
end
|
|
|
|
parsed_hashes = [dynamic_attributes.new, dynamic_attributes.old].compact.map do |attribute_hash|
|
|
unless (hash = AttributeParser.parse(attribute_hash))
|
|
return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
|
|
end
|
|
hash
|
|
end
|
|
attribute_values = build_attribute_values(attributes, parsed_hashes)
|
|
AttributeBuilder.verify_attribute_names!(attribute_values.map(&:key))
|
|
|
|
values_by_base_key = attribute_values.group_by(&:base_key)
|
|
[:multi, *values_by_base_key.keys.sort.map { |base_key|
|
|
compile_attribute_values(values_by_base_key[base_key])
|
|
}]
|
|
end
|
|
|
|
private
|
|
|
|
# Returns array of AttributeValue instances from static attributes and dynamic_attributes. For each key,
|
|
# the values' order in returned value is preserved in the same order as Haml::Buffer#attributes's merge order.
|
|
#
|
|
# @param attributes [{ String => String }]
|
|
# @param parsed_hashes [{ String => String }]
|
|
# @return [Array<AttributeValue>]
|
|
def build_attribute_values(attributes, parsed_hashes)
|
|
[].tap do |attribute_values|
|
|
attributes.each do |key, static_value|
|
|
attribute_values << AttributeValue.new(:static, key, static_value)
|
|
end
|
|
parsed_hashes.each do |parsed_hash|
|
|
parsed_hash.each do |key, dynamic_value|
|
|
attribute_values << AttributeValue.new(:dynamic, key, dynamic_value)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Compiles attribute values with the same base_key to Temple expression.
|
|
#
|
|
# @param values [Array<AttributeValue>] `base_key`'s results are the same. `key`'s result may differ.
|
|
# @return [Array] Temple expression
|
|
def compile_attribute_values(values)
|
|
if values.map(&:key).uniq.size == 1
|
|
compile_attribute(values.first.key, values)
|
|
else
|
|
runtime_build(values)
|
|
end
|
|
end
|
|
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [Array] Temple expression
|
|
def runtime_build(values)
|
|
hash_content = values.group_by(&:key).map do |key, values_for_key|
|
|
"#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
|
|
end.join(', ')
|
|
[:dynamic, "_hamlout.attributes({ #{hash_content} }, nil)"]
|
|
end
|
|
|
|
# Renders attribute values statically.
|
|
#
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [Array] Temple expression
|
|
def static_build(values)
|
|
hash_content = values.group_by(&:key).map do |key, values_for_key|
|
|
"#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
|
|
end.join(', ')
|
|
|
|
arguments = [@is_html, @attr_wrapper, @escape_attrs, @hyphenate_data_attrs]
|
|
code = "::Haml::AttributeBuilder.build_attributes"\
|
|
"(#{arguments.map { |a| Haml::Util.inspect_obj(a) }.join(', ')}, { #{hash_content} })"
|
|
[:static, eval(code).to_s]
|
|
end
|
|
|
|
# @param key [String]
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [String]
|
|
def merged_value(key, values)
|
|
if values.size == 1
|
|
values.first.to_literal
|
|
else
|
|
"::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&:to_literal).join(', ')})"
|
|
end
|
|
end
|
|
|
|
# @param str [String]
|
|
# @return [String]
|
|
def frozen_string(str)
|
|
"#{Haml::Util.inspect_obj(str)}.freeze"
|
|
end
|
|
|
|
# Compiles attribute values for one key to Temple expression that generates ` key='value'`.
|
|
#
|
|
# @param key [String]
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [Array] Temple expression
|
|
def compile_attribute(key, values)
|
|
if values.all? { |v| Temple::StaticAnalyzer.static?(v.to_literal) }
|
|
return static_build(values)
|
|
end
|
|
|
|
case key
|
|
when 'id', 'class'
|
|
compile_id_or_class_attribute(key, values)
|
|
else
|
|
compile_common_attribute(key, values)
|
|
end
|
|
end
|
|
|
|
# @param id_or_class [String] "id" or "class"
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [Array] Temple expression
|
|
def compile_id_or_class_attribute(id_or_class, values)
|
|
var = unique_name
|
|
[:multi,
|
|
[:code, "#{var} = (#{merged_value(id_or_class, values)})"],
|
|
[:case, var,
|
|
['Hash, Array', runtime_build([AttributeValue.new(:dynamic, id_or_class, var)])],
|
|
['false, nil', [:multi]],
|
|
[:else, [:multi,
|
|
[:static, " #{id_or_class}=#{@attr_wrapper}"],
|
|
[:escape, @escape_attrs, [:dynamic, var]],
|
|
[:static, @attr_wrapper]],
|
|
]
|
|
],
|
|
]
|
|
end
|
|
|
|
# @param key [String] Not "id" or "class"
|
|
# @param values [Array<AttributeValue>]
|
|
# @return [Array] Temple expression
|
|
def compile_common_attribute(key, values)
|
|
var = unique_name
|
|
[:multi,
|
|
[:code, "#{var} = (#{merged_value(key, values)})"],
|
|
[:case, var,
|
|
['Hash', runtime_build([AttributeValue.new(:dynamic, key, var)])],
|
|
['true', true_value(key)],
|
|
['false, nil', [:multi]],
|
|
[:else, [:multi,
|
|
[:static, " #{key}=#{@attr_wrapper}"],
|
|
[:escape, @escape_attrs, [:dynamic, var]],
|
|
[:static, @attr_wrapper]],
|
|
]
|
|
],
|
|
]
|
|
end
|
|
|
|
def true_value(key)
|
|
if @is_html
|
|
[:static, " #{key}"]
|
|
else
|
|
[:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
|
|
end
|
|
end
|
|
|
|
def unique_name
|
|
@unique_name ||= 0
|
|
"_haml_attribute_compiler#{@unique_name += 1}"
|
|
end
|
|
end
|
|
end
|