lib/hexp/node/rewriter.rb

Summary

Maintainability
A
55 mins
Test Coverage
module Hexp
  class Node
    # Create a new Hexp node based on an existing node
    #
    # Rewriting in this case means iterating over the whole Hexp tree, and for
    # each element providing zero or more elements to replace it with.
    #
    class Rewriter
      include Hexp

      # Initialize a rewriter with the node to operate on, and the action
      #
      # @param [Hexp::Node] node
      #   The root node of the tree to be altered
      # @param [Proc] block
      #   The action to perform on each node
      #
      # @api public
      def initialize(node, block)
        @node, @block = node, block
      end

      # Implicit Hexp conversion protocol
      #
      # A {Rewriter} is lazy, only when one of the {Hexp::DSL} methods is used,
      # does the rewriting happen.
      #
      # @return [Hexp::Node]
      #
      # @api public
      def to_hexp
        @hexp ||= H[
          @node.tag,
          @node.attributes,
          @block ? rewrite_children : @node.children
        ]
      end

      private

      # Helper for rewrite
      #
      # @return [Array<Hexp::Node>]
      #
      # @api private
      def rewrite_children
        @node.children
          .flat_map {|child| child.rewrite(&@block)   }
          .flat_map {|child| coerce_rewrite_response(@block.(child.to_hexp, @node)) || [child] }
      end

      # Turn the response of a rewrite operation into something value
      #
      # The response can be a list of nodes, or a single node. If the response is
      # `nil`, that is interpreted as removing the node.
      #
      # @param [nil,#to_hexp,#to_str,#to_ary] response
      #
      # @return [Array<Hexp::Node>]
      #
      # @api private
      def coerce_rewrite_response(response)
        return [] if response.nil?

        return [response.to_hexp] if response.respond_to? :to_hexp
        return [response.to_str]  if response.respond_to? :to_str

        if response.respond_to? :to_ary
          return [response] if response.first.is_a? Symbol
          return response.to_ary
        end

        raise FormatError, "invalid rewrite response : #{response.inspect}, expected #{self.class} or Array, got #{response.class}"
      end
    end
  end
end