class Hashery::PathHash

A PathHash is a hash whose values can be accessed in the normal manner, or with keys that are slash (‘/`) separated strings. To get the whole hash as a single flattened level, call `#flat`. All keys are converted to strings. All end-of-the-chain values are kept in whatever value they are.

s = PathHash['a' => 'b', 'c' => {'d' => :e}]
s['a'] #=> 'b'
s['c'] #=> {slashed: 'd'=>:e}
s['c']['d'] #=> :e
s['c/d'] #=> :e

PathHash is derived from the SlashedHash class in the HashMagic project by Daniel Parker <gems@behindlogic.com>.

Copyright © 2006 BehindLogic (hash_magic.rubyforge.org)

Authors: Daniel Parker

TODO: This class is very much a work in progess and will be substantially rewritten for future versions.

Public Class Methods

new(hsh={}) click to toggle source

Initialize PathHash.

hsh - Priming Hash.

# File lib/hashery/path_hash.rb, line 31
def initialize(hsh={})
  raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array)
  @constructor = hsh.is_a?(Hash) ? hsh.class : Hash
  @flat = flatten_to_hash(hsh)
end

Public Instance Methods

==(other) click to toggle source
# File lib/hashery/path_hash.rb, line 180
def ==(other)
  case other
  when SlashedHash
    @slashed == other.instance_variable_get(:@slashed)
  when Hash
    self == SlashedHash.new(other)
  else
    raise TypeError, "Cannot compare #{other.class.name} with SlashedHash"
  end
end
[](key) click to toggle source

Behaves like the usual Hash#[] method, but you can access nested hash values by composing a single key of the traversing keys joined by ‘/’:

hash['c']['d'] # is the same as:
hash['c/d']
# File lib/hashery/path_hash.rb, line 46
def [](key)
  rg = Regexp.new("^#{key}/?")
  start_obj = if @constructor == OrderedHash
    @constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')})
  else
    @constructor.new
  end
  v = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ rg)}.inject(start_obj) {|h,(k,v)| h[k.gsub(rg,'')] = v; h})
  v.is_a?(self.class) && v.empty? ? nil : v
end
[]=(key,value) click to toggle source

Same as above, except sets value rather than retrieving it.

# File lib/hashery/path_hash.rb, line 60
def []=(key,value)
  @flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")}
  if value.is_a?(Hash)
    flatten_to_hash(value).each do |hk,hv|
      @flat[key.to_s+'/'+hk.to_s] = hv
    end
  else
    @flat[key.to_s] = value
  end
end
delete(key,&block) click to toggle source

Delete entry from Hash. Slashed keys can be used here, too.

key - The key to delete. block - Produces the return value if key not found.

Returns delete value.

# File lib/hashery/path_hash.rb, line 104
def delete(key,&block)
  value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h})
  return block.call(key) if value.is_a?(self.class) && value.empty? && block_given?
  @flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)}
  return value
end
empty?() click to toggle source
# File lib/hashery/path_hash.rb, line 112
def empty?
  @flat.empty?
end
expand() click to toggle source

Expands the whole hash to Hash objects … not useful very often, it seems.

# File lib/hashery/path_hash.rb, line 147
def expand
  inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h}
end
flat() click to toggle source

Gives a list of all keys in all levels in the multi-level hash, joined by slashes.

{'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys
#=> ['a/b', 'a/c', 'b']
# File lib/hashery/path_hash.rb, line 142
def flat
  @flat
end
index(value) click to toggle source

This gives you the slashed key of the value, no matter where the value is in the tree.

# File lib/hashery/path_hash.rb, line 117
def index(value)
  @flat.index(value)
end
inspect() click to toggle source
# File lib/hashery/path_hash.rb, line 122
def inspect
  @flat.inspect.insert(1,'slashed: ')
end
keys() click to toggle source

This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys

# File lib/hashery/path_hash.rb, line 127
def keys
  @flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq
end
ordered(*keys_in_order) click to toggle source

Same as ordered! but returns a new SlashedHash object instead of modifying the same.

# File lib/hashery/path_hash.rb, line 160
def ordered(*keys_in_order)
  dup.ordered!(*keys_in_order)
end
ordered!(*keys_in_order) click to toggle source

Sets the SlashedArray as ordered. The *keys_in_order must be a flat array of slashed keys that specify the order for each level:

s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed
s.ordered!('b', 'a/c', 'a/b')
s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}}
# Note that the expanded hashes will *still* be ordered!
# File lib/hashery/path_hash.rb, line 172
def ordered!(*keys_in_order)
  return self if @constructor == OrderedHash
  @constructor = OrderedHash
  @flat = @flat.ordered(*keys_in_order)
  self
end
to_string_array() click to toggle source
# File lib/hashery/path_hash.rb, line 151
def to_string_array
  flatten_to_array(flat,[])
end

Private Instance Methods

flatten_to_array(value,a) click to toggle source
# File lib/hashery/path_hash.rb, line 226
def flatten_to_array(value,a)
  if value.is_a?(Array)
    value.each {|e| flatten_to_array(e,a)}
  elsif value.is_a?(Hash)
    value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e}
  else
    a << value.to_s
  end
  a
end
flatten_to_hash(hsh) click to toggle source
# File lib/hashery/path_hash.rb, line 193
def flatten_to_hash(hsh)
  flat = @constructor.new
  if hsh.is_a?(Array)
    hsh.each do |e|
      flat.merge!(flatten_to_hash(e))
    end
  elsif hsh.is_a?(Hash)
    hsh.each do |k,v|
      if v.is_a?(Hash)
        flatten_to_hash(v).each do |hk,hv|
          flat[k.to_s+'/'+hk.to_s] = hv
        end
      else
        flat[k.to_s] = v
      end
    end
  else
    ks = hsh.split('/',-1)
    v = ks.pop
    ks = ks.join('/')
    if !flat[ks].nil?
      if flat[ks].is_a?(Array)
        flat[ks] << v
      else
        flat[ks] = [flat[ks], v]
      end
    else
      flat[ks] = v
    end
  end
  flat
end