class HashDigger::Digger

Public Class Methods

dig(data:, path:, strict: true, default: nil) { |data| ... } click to toggle source
# File lib/hash_digger.rb, line 14
def dig(data:, path:, strict: true, default: nil)
  if data.respond_to?(:keys)
    data = data.deep_symbolize_keys
  end

  # form the path of non enumerable diggable groups
  path = path(path)

  until path.empty? do
    # *.* stands for "flatten the top level arrays until there's one * left
    # which means iterate the fetcher with the following path over the result of the flattening"
    while path.fetch(0) === '*' && path&.fetch(1, nil) === '*' do
      data = data.flatten(1)
      path.shift
    end

    # if the path ends with a "*" and that's it
    return (block_given? ? yield(data) : data) if path.length === 1 && path.fetch(0) === '*'

    subpath = path.shift

    if subpath === '*'
      subpath = path.shift
      data = data&.collect {|node| fetch(data: node, path: subpath, strict: strict, default: default) }
    else
      data = fetch(data: data, path: subpath, strict: strict, default: default)
    end
  end

  # return data or apply the custom block handler to the whole result
  return (block_given? ? yield(data) : data)
end

Private Class Methods

fetch(data:, path:, strict:, default: nil) click to toggle source
# File lib/hash_digger.rb, line 49
def fetch(data:, path:, strict:, default: nil)
  path.reduce(data) { |node, key|
    node != default ? (node&.fetch(key) { |key| strict ? (raise DigError) : default }) : node
  }
rescue KeyError, TypeError, DigError => e
  return default unless strict

  line = "#{'-'*70}\n"

  # ==== START ::: Intercept Awesome Print output =====
  # TODO: Fix STDOUT interception with threads in mind!!!
  str_io = StringIO.new
  $stdout = str_io

  ap data
  ap_data = "#{line}#{$stdout.string}#{line}"

  $stdout = STDOUT
  # ==== END ::: Intercept Pretty Print output =====

  raise DigError, "\nThere is no `#{path.join(' > ')}` path in some of the children:\n#{ap_data}"
end
path(path) click to toggle source
# File lib/hash_digger.rb, line 72
def path(path)
  path = path.try(:split, '*')

  # split subgroups by "." and coerce types
  path = path.collect do |dp|
    dig_path = dp.try(:split, '.').compact.try(:map) do |p|
      (p =~ /^[\d]+$/) ? p.to_i : p.to_sym
    end

    dig_path.reject! do |p|
      p.try(:blank?)
    end

    dig_path.flatten
  end

  return path if path.length === 1

  path = path.map {|e| e === [] ? ['*'] : [e, '*']}.flatten(1)

  return path unless path.last === '*'

  path
end