k0kubun/hamlit

View on GitHub
lib/hamlit/attribute_builder.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# frozen_string_literal: true
require 'hamlit/object_ref'

module Hamlit::AttributeBuilder
  BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
                       autoplay controls loop selected hidden scoped async
                       defer reversed ismap seamless muted required
                       autofocus novalidate formnovalidate open pubdate
                       itemscope allowfullscreen default inert sortable
                       truespeed typemustmatch download playsinline].freeze

  # Java extension is not implemented for JRuby yet.
  # TruffleRuby does not implement `rb_ary_sort_bang`, etc.
  if /java/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
    class << self
      def build(escape_attrs, quote, format, boolean_attributes, object_ref, *hashes)
        hashes << Hamlit::ObjectRef.parse(object_ref) if object_ref
        buf  = []
        hash = merge_all_attrs(hashes)

        keys = hash.keys.sort!
        keys.each do |key|
          case key
          when 'id'.freeze
            buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
          when 'class'.freeze
            buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
          when 'data'.freeze
            buf << build_data(escape_attrs, quote, *hash[key])
          when *boolean_attributes, /\Adata-/
            build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
          else
            buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
          end
        end
        buf.join
      end

      def build_id(escape_attrs, *values)
        escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
      end

      def build_class(escape_attrs, *values)
        if values.size == 1
          value = values.first
          case
          when value.is_a?(String)
            # noop
          when value.is_a?(Array)
            value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
          when value
            value = value.to_s
          else
            return ''
          end
          return escape_html(escape_attrs, value)
        end

        classes = []
        values.each do |value|
          case
          when value.is_a?(String)
            classes += value.split(' ')
          when value.is_a?(Array)
            classes += value.select { |v| v }
          when value
            classes << value.to_s
          end
        end
        escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
      end

      def build_data(escape_attrs, quote, *hashes)
        build_data_attribute(:data, escape_attrs, quote, *hashes)
      end

      def build_aria(escape_attrs, quote, *hashes)
        build_data_attribute(:aria, escape_attrs, quote, *hashes)
      end

      private

      def build_data_attribute(key, escape_attrs, quote, *hashes)
        attrs = []
        if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
          data_value = merge_all_attrs(hashes)
        else
          data_value = hashes.last
        end
        hash = flatten_attributes(key => data_value)

        hash.sort_by(&:first).each do |key, value|
          case value
          when true
            attrs << " #{key}"
          when nil, false
            # noop
          else
            attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
          end
        end
        attrs.join
      end

      def flatten_attributes(attributes)
        flattened = {}

        attributes.each do |key, value|
          case value
          when attributes
          when Hash
            flatten_attributes(value).each do |k, v|
              if k.nil?
                flattened[key] = v
              else
                flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
              end
            end
          else
            flattened[key] = value if value
          end
        end
        flattened
      end

      def merge_all_attrs(hashes)
        merged = {}
        hashes.each do |hash|
          hash.each do |key, value|
            key = key.to_s
            case key
            when 'id'.freeze, 'class'.freeze, 'data'.freeze
              merged[key] ||= []
              merged[key] << value
            else
              merged[key] = value
            end
          end
        end
        merged
      end

      def build_boolean!(escape_attrs, quote, format, buf, key, value)
        case value
        when true
          case format
          when :xhtml
            buf << " #{key}=#{quote}#{key}#{quote}"
          else
            buf << " #{key}"
          end
        when false, nil
          # omitted
        else
          buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
        end
      end

      def escape_html(escape_attrs, str)
        if escape_attrs
          Hamlit::Utils.escape_html(str)
        else
          str
        end
      end
    end
  else
    # Hamlit::AttributeBuilder.build
    # Hamlit::AttributeBuilder.build_id
    # Hamlit::AttributeBuilder.build_class
    # Hamlit::AttributeBuilder.build_data
    # Hamlit::AttributeBuilder.build_aria
    require 'hamlit/hamlit'
  end
end