class TreeHash

Attributes

children[R]
node_class[R]
parent[R]

Public Class Methods

new(object = {}, parent = nil) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 4
def initialize(object = {}, parent = nil)
  @children = {}
  @parent   = parent
  object    = object.value if object.is_a?(TreeHash)
  replace_with(object)
end

Public Instance Methods

[](key) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 60
def [](key)
  children[key]
end
[]=(key, value) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 64
def []=(key, value)
  add_child(key, value)
end
absolute_path() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 222
def absolute_path
  anc = ancestors[1..-1]
  (anc.nil? ? [] : anc.map(&:path) + [path]).join('.')
end
absolute_paths() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 286
def absolute_paths
  root.paths
end
ancestors() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 216
def ancestors
  return [] if root?
  return [parent] if parent.root?
  parent.ancestors + [parent]
end
bridge(paths) click to toggle source

Generate a path using a dot (.) delimited path e.g. bridge('cats.jackson' => :my_value) This modifies the underlying container in place

# File lib/bblib/core/classes/tree_hash.rb, line 109
def bridge(paths)
  paths = paths.map { |a| [a, nil] }.to_h if paths.is_a?(Array)
  paths.each do |path, value|
    parts     = path.to_s.split(/(?<=[^\\])\./)
    node      = self
    next_part = false
    until next_part.nil?
      part      = next_part || process_bridge_part(parts.shift)
      next_part = process_bridge_part(parts.shift)
      if node.child_exists?(part)
        if next_part.is_a?(Integer)
          next_next = process_bridge_part(parts.first)
          if next_next.is_a?(Integer)
            node[part][next_part] = [] unless node[part][next_part] && node[part][next_part].node_class == Array
          else
            node[part][next_part] = {} unless node[part][next_part] && node[part][next_part].node_class == Hash
          end
        end
        next_part.nil? ? node[part] = value : node = node.child(part)
      else
        if next_part.nil?
          node[part] = value
        else
          node[part] = next_part.is_a?(Integer) ? Array.new(next_part) : {}
        end
        node[part][next_part] = process_bridge_part(parts.first).is_a?(Integer) ? [] : {} if next_part.is_a?(Integer)
        node = node[part]
      end
    end
  end
  self
end
child(key, symbol_sensitive = false) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 24
def child(key, symbol_sensitive = false)
  case
  when Hash >= node_class
    children[key] || (symbol_sensitive ? nil : children[key.to_s.to_sym])
  when Array >= node_class
    children[key.to_i]
  else
    nil
  end
end
child?() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 20
def child?
  !root?
end
child_exists?(key, symbol_sensitive = false) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 35
def child_exists?(key, symbol_sensitive = false)
  case
  when Hash >= node_class
    children.include?(key) || (!symbol_sensitive && children.include?(key.to_s.to_sym))
  when Array >= node_class && key.respond_to?(:to_i)
    [0...children.size] === key.to_i
  else
    false
  end
end
children?() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 46
def children?
  (node_class <= Hash || node_class <= Array) && !children.empty?
end
copy(paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 142
def copy(paths)
  paths.each do |from, to|
    value = find(from).first
    bridge(to => value)
  end
  self
end
copy_all(paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 150
def copy_all(paths)
  paths.each do |from, to|
    value = find(from)
    bridge(to => value)
  end
  self
end
copy_to(hash, *paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 178
def copy_to(hash, *paths)
  hash = TreeHash.new(hash) unless hash.is_a?(TreeHash)
  paths.each do |path|
    hash.bridge(path => find(path).first)
  end
  hash
end
delete(*paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 243
def delete(*paths)
  paths.flat_map do |path|
    find(path).map do |child|
      if child.root?
        delete_child(path)
      else
        child.parent.delete_child(child.key)
      end
    end
  end
end
delete_child(key, symbol_sensitive = false) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 328
def delete_child(key, symbol_sensitive = false)
  case
  when Hash >= node_class
    child = symbol_sensitive ? nil : children.delete(key.to_s.to_sym)
    child = children.delete(key) unless child
  when Array >= node_class
    children.delete(key.to_i)
  else
    nil
  end
end
descendants() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 50
def descendants
  return [] unless children?
  desc = []
  children.each do |_key, child|
    desc << child
    desc += child.descendants if child.children?
  end
  desc
end
find(path) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 68
def find(path)
  path = HashPath.new(*path) unless path.is_a?(HashPath)
  matches = [self]
  path.parts.each do |part|
    break if matches.empty?
    matches = matches.flat_map do |match|
      part.matches(match)
    end
  end
  matches
end
find_join(*paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 86
def find_join(*paths)
  results = find_multi(*paths)
  (0..(results.max_by(&:size).size - 1)).map do |index|
    results.map do |result|
      result[index]
    end
  end
end
find_join_hash(key_path, val_path) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 95
def find_join_hash(key_path, val_path)
  find_join(key_path, val_path).to_h
end
find_multi(*paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 80
def find_multi(*paths)
  paths.map do |path|
    find(path)
  end
end
following_siblings(limit = 0) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 302
def following_siblings(limit = 0)
  siblings[(index + 1)..-(limit.to_i + 1)]
end
index() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 239
def index
  parent.children.values.index(self)
end
inspect() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 227
def inspect
  value
end
key() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 270
def key
  return nil if root?
  case
  when Hash >= parent.node_class
    parent.keys[index]
  when Array >= parent.node_class
    index
  else
    nil
  end
end
keys() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 297
def keys
  return [] unless @children.respond_to?(:keys)
  @children.keys
end
kill() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 255
def kill
  root.delete(absolute_path)
end
leaf_children() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 290
def leaf_children
  return self unless children?
  children.map do |k, v|
    v.children? ? v.leaf_children : v
  end.flatten
end
move(paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 158
def move(paths)
  paths.each do |from, to|
    values = find(from)
    next if values.empty?
    value = values.first
    bridge(to => value)
    value.kill if value
  end
  self
end
move_all(paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 169
def move_all(paths)
  paths.each do |from, to|
    value = find(from)
    bridge(to => value)
    value.map(&:kill)
  end
  self
end
move_to(hash, *paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 186
def move_to(hash, *paths)
  hash = TreeHash.new(hash) unless hash.is_a?(TreeHash)
  paths.each do |path|
    value = find(path).first
    hash.bridge(path => value)
    value.kill if value
  end
  hash
end
next_sibling() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 315
def next_sibling
  sibling(1)
end
path() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 282
def path
  parent.node_class == Array ? "[#{key}]" : key.to_s.gsub('.', '\\.')
end
paths() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 208
def paths
  if Array >= node_class || Hash >= node_class
    value.squish.keys
  else
    []
  end
end
preceeding_siblings(limit = 0) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 306
def preceeding_siblings(limit = 0)
  limit = limit.to_i
  siblings[(limit.zero? ? limit : (index - limit))..(index - 1)]
end
previous_sibling() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 319
def previous_sibling
  sibling(-1)
end
process(processor, &block) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 200
def process(processor, &block)
  # TODO: Add ability to process values or keys in tree hashes
end
replace_with(object) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 11
def replace_with(object)
  @node_class = object.class
  build_from(object)
end
root() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 323
def root
  return self if root?
  ancestors.find(&:root?)
end
root?() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 16
def root?
  @parent.nil?
end
set(paths) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 99
def set(paths)
  paths.each do |path, value|
    find(path).each { |child| child.replace_with(value) }
  end
  self
end
sibling(offset) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 311
def sibling(offset)
  siblings[index + offset.to_i]
end
siblings() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 235
def siblings
  parent.children.values.map { |c| c == self ? nil : c }.compact
end
size() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 204
def size
  @children.respond_to?(:size) ? @children.size : 1
end
to_s() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 231
def to_s
  value.to_s
end
to_tree_hash() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 196
def to_tree_hash
  self
end
value() click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 259
def value
  case
  when Hash >= node_class
    children.hmap { |k, v| [k, v.value] }
  when Array >= node_class
    children.values.map(&:value)
  else
    children
  end
end

Protected Instance Methods

add_child(key, child) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 354
def add_child(key, child)
  if Array >= node_class && !key.is_a?(Integer)
    # If the class was an Array but a hash key was passed we need to replace this
    # node entirely
    replace_with(key => child)
  else
    @children[key] = TreeHash.new(child, self)
  end
end
build_from(object) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 342
def build_from(object)
  @children = {}
  case
  when object.class <= Hash
    object.each { |k, v| @children[k] = TreeHash.new(v, self) }
  when object.class <= Array
    object.each_with_index { |a, i| @children[i] = TreeHash.new(a, self) }
  else
    @children = object
  end
end
process_bridge_part(part) click to toggle source
# File lib/bblib/core/classes/tree_hash.rb, line 364
def process_bridge_part(part)
  return unless part
  part =~ /^\[\d+\]$/ ? part.uncapsulate('[').to_i : part.gsub('\\.', '.').to_sym
end