Class: Hashery::PathHash

Inherits:
Hash show all
Defined in:
lib/hashery/path_hash.rb

Overview

TODO:

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

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 <[email protected]>.

Copyright © 2006 BehindLogic (hash_magic.rubyforge.org)

Instance Method Summary collapse

Methods inherited from Hash

create, #rekey, #rekey!, #retrieve, #to_hash, #to_stash

Constructor Details

#initialize(hsh = {}) ⇒ PathHash

Initialize PathHash.

Parameters:

  • hsh (defaults to: {})

    Priming Hash.

Raises:

  • (ArgumentError)


31
32
33
34
35
# 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

Instance Method Details

#==(other) ⇒ Object



180
181
182
183
184
185
186
187
188
189
# 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) ⇒ 'c/d'

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']

Returns:

  • ('c/d')


46
47
48
49
50
51
52
53
54
55
# 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) ⇒ Object

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



60
61
62
63
64
65
66
67
68
69
# 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

#clearObject

:nodoc:



71
72
73
# File 'lib/hashery/path_hash.rb', line 71

def clear # :nodoc:
  @flat.clear
end

#delete(key, &block) ⇒ Object

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

Parameters:

  • key

    The key to delete.

  • block

    Produces the return value if key not found.

Returns:

  • delete value.



104
105
106
107
108
109
# 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?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/hashery/path_hash.rb', line 112

def empty?
  @flat.empty?
end

#expandObject

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



147
148
149
# 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

#fetch(key, default = :ehisehoah0928309q98y30, &block) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/hashery/path_hash.rb', line 78

def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc:
  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})
  if value.is_a?(self.class) && value.empty?
    if default == :ehisehoah0928309q98y30
      if block_given?
        block.call(key)
      else
        raise IndexError
      end
      value
    else
      default
    end
  else
    value
  end
end

#flat'a/b', ...

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

‘c’=>‘d’, ‘b’=>‘c’}.slashed.flat.keys #=> [‘a/b’, ‘a/c’, ‘b’]

Returns:

  • ('a/b', 'a/c', 'b')


142
143
144
# File 'lib/hashery/path_hash.rb', line 142

def flat
  @flat
end

#flatten_to_array(value, a) ⇒ Object (private)



226
227
228
229
230
231
232
233
234
235
# 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) ⇒ Object (private)



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# 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

#index(value) ⇒ Object

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



117
118
119
# File 'lib/hashery/path_hash.rb', line 117

def index(value)
  @flat.index(value)
end

#inspectObject



122
123
124
# File 'lib/hashery/path_hash.rb', line 122

def inspect
  @flat.inspect.insert(1,'slashed: ')
end

#keysObject

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



127
128
129
# 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) ⇒ Object

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



160
161
162
# File 'lib/hashery/path_hash.rb', line 160

def ordered(*keys_in_order)
  dup.ordered!(*keys_in_order)
end

#ordered!(*keys_in_order) ⇒ Object

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!


172
173
174
175
176
177
# 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

#rehashObject

This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you end up with an array holding several hashes.



133
134
135
# File 'lib/hashery/path_hash.rb', line 133

def rehash # :nodoc:
  @flat.rehash
end

#slashedObject

:nodoc:



155
156
157
# File 'lib/hashery/path_hash.rb', line 155

def slashed # :nodoc:
  self
end

#to_string_arrayObject



151
152
153
# File 'lib/hashery/path_hash.rb', line 151

def to_string_array
  flatten_to_array(flat,[])
end