lib/temple/html/pretty.rb



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
        @last = nil
        @indent = 0
        @pretty = options[:pretty]
        @pre_tags = Regexp.new(options[:pre_tags].map {|t| "<#{t}" }.join('|'))
      end

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

      def on_static(content)
        if @pretty
          if @pre_tags !~ content
            content = content.sub(/\A\s*\n?/, "\n") if options[:indent_tags].include?(@last)
            content = content.gsub("\n", indent)
          end
          @last = :static
        end
        [:static, content]
      end

      def on_dynamic(code)
        if @pretty
          tmp = unique_name
          indent_code = ''
          indent_code << "#{tmp} = #{tmp}.sub(/\\A\\s*\\n?/, \"\\n\"); " if options[:indent_tags].include?(@last)
          indent_code << "#{tmp} = #{tmp}.gsub(\"\n\", #{indent.inspect}); "
          if ''.respond_to?(:html_safe)
            safe = unique_name
            # we have to first save if the string was html_safe
            # otherwise the gsub operation will lose that knowledge
            indent_code = "#{safe} = #{tmp}.html_safe?; #{indent_code}#{tmp} = #{tmp}.html_safe if #{safe}; "
          end
          @last = :dynamic
          [:multi,
           [:code, "#{tmp} = (#{code}).to_s"],
           [:code, "if #{@pre_tags_name} !~ #{tmp}; #{indent_code}end"],
           [:dynamic, tmp]]
        else
          [:dynamic, code]
        end
      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]
        @last = :comment
        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 && xhtml? ? ' /' : '') + '>']

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

        @pretty = true
        result
      end

      protected

      def preamble
        @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)
        result = @last && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
        @last = name
        result
      end
    end
  end
end