#--
# Special thanks to Alison Rowland, Jamie Macey and Mat Schaffer
# for inspiring recursive imporvements.
#++

require 'ostruct'
require 'facets/core/ostruct/__update__'

class Hash

  # Like to_ostruct but recusively objectifies all hash elements as well.
  #
  #     o = { 'a' => { 'b' => 1 } }.to_ostruct_recurse
  #     o.a.b  #=> 1
  #
  # The +exclude+ parameter is used internally to prevent infinite
  # recursion and is not intended to be utilized by the end-user.
  # But for more advance use, if there is a particular subhash you
  # would like to prevent from being converted to an OpoenStruct
  # then include it in the +exclude+ hash referencing itself. Eg.
  #
  #     h = { 'a' => { 'b' => 1 } }
  #     o = h.to_ostruct_recurse( { h['a'] => h['a'] } )
  #     o.a['b']  #=> 1
  #
  def to_ostruct_recurse( exclude={} )
    return exclude[self] if exclude.key?( self )
    o = exclude[self] = OpenStruct.new
    h = self.dup
    each_pair do |k,v|
      h[k] = v.to_ostruct_recurse( exclude ) if v.respond_to?( :to_ostruct_recurse )
    end
    o.__update__( h )
  end

end



#  _____         _
# |_   _|__  ___| |_
#   | |/ _ \/ __| __|
#   | |  __/\__ \ |_
#   |_|\___||___/\__|
#
=begin test

  require 'test/unit'

  class TCHash < Test::Unit::TestCase

    def test_to_ostruct_recurse
      a = { :a => 1, :b => 2, :c => { :x => 4 } }
      ao = a.to_ostruct_recurse
      assert_equal(  a[:a], ao.a )
      assert_equal(  a[:b], ao.b )
      assert_equal(  a[:c][:x], ao.c.x )
    end

    def test_to_ostruct_recurse_with_recursion
      a = {}
      a[:a] = a
      ao = a.to_ostruct_recurse
      assert_equal( ao, ao.a )
    end

    def test_to_ostruct_advanced
      h = { 'a' => { 'b' => 1 } }
      o = h.to_ostruct_recurse( { h['a'] => h['a'] } )
      assert_equal( 1, o.a['b'] )
      assert( Hash === o.a )
    end

  end

=end
