lib/temple/html/pretty.rb



# frozen_string_literal: true
module Temple
  module HTML
    # @api public
    class Pretty < Fast
      define_options indent: '  ',
                     pretty: true,
                     indent_tags: %w(article aside audio base body datalist dd div dl dt
                                     fieldset figure footer form head h1 h2 h3 h4 h5 h6
                                     header hgroup hr html li link meta nav ol option p
                                     rp rt ruby section script style table tbody td tfoot
                                     th thead tr ul video doctype).freeze,
                     pre_tags: %w(code pre textarea).freeze

      def initialize(opts = {})
        super
        @indent_next = nil
        @indent = 0
        @pretty = options[:pretty]
        @pre_tags = @format != :xml && Regexp.union(options[:pre_tags].map {|t| "<#{t}" })
      end

      def call(exp)
        @pretty ? [:multi, preamble, compile(exp)] : super
      end

      def on_static(content)
        return [:static, content] unless @pretty
        unless @pre_tags && @pre_tags =~ content
          content = content.sub(/\A\s*\n?/, "\n".freeze) if @indent_next
          content = content.gsub("\n".freeze, indent)
        end
        @indent_next = false
        [:static, content]
      end

      def on_dynamic(code)
        return [:dynamic, code] unless @pretty
        indent_next, @indent_next = @indent_next, false
        [:dynamic, "::Temple::Utils.indent_dynamic((#{code}), #{indent_next.inspect}, #{indent.inspect}#{@pre_tags ? ', ' + @pre_tags_name : ''})"]
      end

      def on_html_doctype(type)
        return super unless @pretty
        [:multi, [:static, tag_indent('doctype')], super]
      end

      def on_html_comment(content)
        return super unless @pretty
        result = [:multi, [:static, tag_indent('comment')], super]
        @indent_next = false
        result
      end

      def on_html_tag(name, attrs, content = nil)
        return super unless @pretty

        name = name.to_s
        closed = !content || (empty_exp?(content) && options[:autoclose].include?(name))

        @pretty = false
        result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)]
        result << [:static, (closed && @format != :html ? ' /' : '') + '>']

        @pretty = !@pre_tags || !options[:pre_tags].include?(name)
        if content
          @indent += 1
          result << compile(content)
          @indent -= 1
        end
        unless closed
          indent = tag_indent(name)
          result << [:static, "#{content && !empty_exp?(content) ? indent : ''}</#{name}>"]
        end
        @pretty = true
        result
      end

      protected

      def preamble
        return [:multi] unless @pre_tags
        @pre_tags_name = unique_name
        [:code, "#{@pre_tags_name} = /#{@pre_tags.source}/"]
      end

      def indent
        "\n" + (options[:indent] || '') * @indent
      end

      # Return indentation before tag
      def tag_indent(name)
        if @format == :xml
          flag = @indent_next != nil
          @indent_next = true
        else
          flag = @indent_next != nil && (@indent_next || options[:indent_tags].include?(name))
          @indent_next = options[:indent_tags].include?(name)
        end
        flag ? indent : ''
      end
    end
  end
end