shard-spectator/src/spectator/filtered_example_iterator.cr

86 lines
3.0 KiB
Crystal

require "./example"
require "./node"
require "./node_filter"
require "./node_iterator"
module Spectator
# Iterates through selected nodes in a group and its nested groups.
# Nodes are iterated in pre-order.
class FilteredExampleIterator
include Iterator(Example)
# A stack is used to track where in the tree this iterator is.
@stack = Deque(Node).new(1)
# A queue stores forced examples that have been matched by the a parent group.
@queue = Deque(Example).new
# Creates a new iterator.
# The *group* is the example group to iterate through.
# The *filter* selects which examples (and groups) to iterate through.
def initialize(@group : Node, @filter : NodeFilter)
@stack.push(@group)
end
# Retrieves the next selected `Example`.
# If there are none left, then `Iterator::Stop` is returned.
def next
# Return items from the queue first before continuing to the stack.
return @queue.shift unless @queue.empty?
# Keep going until either:
# a. a suitable example is found.
# b. the stack is empty.
until @stack.empty?
# Retrieve the next node.
node = @stack.pop
# If the node is a group, conditionally traverse it.
if node.is_a?(Indexable(Node))
# To traverse, a child node or the group itself must match the filter.
return node if node = next_group_match(node)
elsif node.is_a?(Example) && @filter.includes?(node)
return node
end
end
# Nothing left to iterate.
stop
end
# Restart the iterator at the beginning.
def rewind
@stack.clear
@stack.push(@group)
@queue.clear
self
end
# Attempts to find the next matching example in a group.
# If any child in the group matches, then traversal on the stack (down the tree) continues.
# However, if no children match, but the group itself does, then all examples in the group match.
# In the latter scenario, the examples are added to the queue, and the next item from the queue returned.
# Stack iteration should continue if nil is returned.
private def next_group_match(group : Indexable(Node)) : Example?
# Look for any children that match.
iterator = NodeIterator.new(group)
# Check if any children match.
# Skip first node because its the group being checked.
if iterator.skip(1).any?(@filter)
# Add the group's direct children to the queue
# in reverse order so that the tree is traversed in pre-order.
group.reverse_each { |node| @stack.push(node) }
# Check if the group matches, but no children match.
elsif @filter.includes?(group)
# Add examples from the group to the queue.
# Return the next example from the queue.
iterator.rewind.select(Example).each { |node| @queue.push(node) }
@queue.shift unless @queue.empty?
# If the queue is empty (group has no examples), go to next loop iteration of the stack.
end
end
end
end