Module: Abstractivator::Trees

Defined in:
lib/abstractivator/trees/tree_map.rb,
lib/abstractivator/trees/tree_compare.rb,
lib/abstractivator/trees/block_collector.rb,
lib/abstractivator/trees/recursive_delete.rb

Defined Under Namespace

Classes: BlockCollector, SetMask, TransformTreeClosure

Instance Method Summary collapse

Instance Method Details

#recursive_delete!(hash, keys) ⇒ Object

recursively deletes the specified keys



9
10
11
12
13
14
15
16
17
18
19
# 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) ⇒ Object



11
12
13
# 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) ⇒ Object

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.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 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| ... } ⇒ Object

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.

Yields:

  • (config)

Raises:

  • (ArgumentError)


14
15
16
17
18
19
# 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