module Abstractivator::Trees

Constants

SetMask

Public Instance Methods

recursive_delete!(hash, keys) click to toggle source

recursively deletes the specified keys

# File lib/abstractivator/trees/recursive_delete.rb, line 9
def recursive_delete!(hash, keys)
  x = hash # hash is named 'hash' for documentation purposes but may be anything
  case x
    when Hash
      keys.each{|k| x.delete(k)}
      x.each_value{|v| recursive_delete!(v, keys)}
    when Array
      x.each{|v| recursive_delete!(v, keys)}
  end
  x
end
set_mask(items, get_key) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 11
def set_mask(items, get_key)
  SetMask.new(items, get_key)
end
tree_compare(tree, mask, path=[], index=nil) click to toggle source

Compares a tree to a mask. Returns a diff of where the tree differs from the mask. Ignores parts of the tree not specified in the mask.

# File lib/abstractivator/trees/tree_compare.rb, line 18
def tree_compare(tree, mask, path=[], index=nil)
  if mask == [:*] && tree.is_a?(Enumerable)
    []
  elsif mask == :+ && tree != :__missing__
    []
  elsif mask == :- && tree != :__missing__
    [diff(path, tree, :__absent__)]
  elsif mask.callable?
    are_equivalent = mask.call(tree)
    are_equivalent ? [] : [diff(path, tree, mask)]
  else
    case mask
      when Hash
        if tree.is_a?(Hash)
          mask.each_pair.flat_map do |k, v|
            tree_compare(tree.fetch(k, :__missing__), v, push_path(path, k))
          end
        else
          [diff(path, tree, mask)]
        end
      when SetMask # must check this before Enumerable because Structs are enumerable
        if tree.is_a?(Enumerable)
          # convert the enumerables to hashes, then compare those hashes
          tree_items = tree
          mask_items = mask.items.dup
          get_key = mask.get_key

          be_strict = !mask_items.delete(:*)
          new_tree = hashify_set(tree_items, get_key)
          new_mask = hashify_set(mask_items, get_key)
          tree_keys = Set.new(new_tree.keys)
          mask_keys = Set.new(new_mask.keys)
          tree_only = tree_keys - mask_keys

          # report duplicate keys
          if new_tree.size < tree_items.size
            diff(path, [:__duplicate_keys__, duplicates(tree_items.map(&get_key))], nil)
          elsif new_mask.size < mask_items.size
            diff(path, nil, [:__duplicate_keys__, duplicates(mask_items.map(&get_key))])
            # hash comparison allows extra values in the tree.
            # report extra values in the tree unless there was a :* in the mask
          elsif be_strict && tree_only.any?
            tree_only.map{|k| diff(push_path(path, k), new_tree[k], :__absent__)}
          else # compare as hashes
            tree_compare(new_tree, new_mask, path, index)
          end
        else
          [diff(path, tree, mask.items)]
        end
      when Enumerable
        if tree.is_a?(Enumerable)
          index ||= 0
          if !tree.any? && !mask.any?
            []
          elsif !tree.any?
            [diff(push_path(path, index.to_s), :__missing__, mask)]
          elsif !mask.any?
            [diff(push_path(path, index.to_s), tree, :__absent__)]
          else
            # if the mask is programmatically generated (unlikely), then
            # the mask might be really big and this could blow the stack.
            # don't support this case for now.
            tree_compare(tree.first, mask.first, push_path(path, index.to_s)) +
                tree_compare(tree.drop(1), mask.drop(1), path, index + 1)
          end
        else
          [diff(path, tree, mask)]
        end
      else
        tree == mask ? [] : [diff(path, tree, mask)]
    end
  end
end
tree_map(h) { |config| ... } click to toggle source

Transforms a tree at certain paths. The transform is non-destructive and reuses untouched substructure. For efficiency, it first builds a “path_tree” that describes which paths to transform. This path_tree is then used as input for a data-driven algorithm.

# File lib/abstractivator/trees/tree_map.rb, line 14
def tree_map(h)
  raise ArgumentError.new('Must provide a transformer block') unless block_given?
  config = BlockCollector.new
  yield(config)
  TransformTreeClosure.new(config).do_obj(h, config.get_path_tree)
end

Private Instance Methods

diff(path, tree, mask) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 110
def diff(path, tree, mask)
  {path: path_string(path), tree: tree, mask: massage_mask_for_diff(mask)}
end
duplicates(xs) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 98
def duplicates(xs)
  xs.group_by{|x| x}.each_pair.select{|_k, v| v.size > 1}.map(&:first)
end
hashify_set(items, get_key) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 94
def hashify_set(items, get_key)
  Hash[items.map{|x| [get_key.call(x), x] }]
end
massage_mask_for_diff(mask) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 114
def massage_mask_for_diff(mask)
  if mask.callable?
    :__predicate__
  else
    mask
  end
end
path_string(path) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 106
def path_string(path)
  path.join('/')
end
push_path(path, name) click to toggle source
# File lib/abstractivator/trees/tree_compare.rb, line 102
def push_path(path, name)
  path + [name]
end