squeeze 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/squeeze/hash_tree.rb +162 -118
- metadata +3 -3
data/lib/squeeze/hash_tree.rb
CHANGED
@@ -1,153 +1,197 @@
|
|
1
1
|
require "squeeze/traversable"
|
2
2
|
|
3
|
-
# A Hash
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
# A Hash specially equipped with a default_proc that wishes into being
|
4
|
+
# new HashTrees when you try to get or set a non-existent key.
|
5
|
+
#
|
6
|
+
# ht = HashTree.new
|
7
|
+
# ht[:i][:am][:a][:roving] = :gambler
|
8
|
+
#
|
9
|
+
class Squeeze
|
10
|
+
class HashTree < Hash
|
11
|
+
include Squeeze::Traversable
|
12
|
+
|
13
|
+
# Override the constructor to provide a default_proc
|
14
|
+
# NOTE: there's a better way to do this in >=1.9.2, it seems.
|
15
|
+
# See Hash#default_proc=
|
16
|
+
def self.new()
|
17
|
+
hash = Hash.new { |h,k| h[k] = HashTree.new }
|
18
|
+
super.replace(hash)
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def self.[](hash)
|
22
|
+
ht = self.new
|
23
|
+
ht << hash
|
24
|
+
ht
|
25
|
+
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
def _dump(depth)
|
28
|
+
h = Hash[self]
|
29
|
+
h.delete_if {|k,v| v.is_a? Proc }
|
30
|
+
Marshal.dump(h)
|
31
|
+
end
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
def self._load(*args)
|
34
|
+
h = Marshal.load(*args)
|
35
|
+
ht = self.new
|
36
|
+
ht.replace(h)
|
37
|
+
ht
|
38
|
+
end
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
# Follow the path specified, creating new nodes where necessary.
|
41
|
+
# Returns the value at the end of the path. If a block is supplied,
|
42
|
+
# it will be called with the last node and the last key as parameters,
|
43
|
+
# analogous to Hash.new's default proc. This is necessary to allow
|
44
|
+
# setting a value at the end of the path. See the implementation of #insert.
|
45
|
+
def create_path(sig)
|
46
|
+
final_key = sig.pop
|
47
|
+
hash = self
|
48
|
+
sig.each do |a|
|
49
|
+
hash = hash[a]
|
50
|
+
end
|
51
|
+
yield(hash, final_key) if block_given?
|
52
|
+
hash[final_key]
|
45
53
|
end
|
46
|
-
yield(hash, final_key) if block_given?
|
47
|
-
hash[final_key]
|
48
|
-
end
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
# Attempt to retrieve the value at the end of the path specified,
|
56
|
+
# without creating new nodes. Returns nil on failure.
|
57
|
+
def find(sig)
|
58
|
+
stage = self
|
59
|
+
sig.each do |a|
|
60
|
+
if stage.has_key?(a)
|
61
|
+
stage = stage[a]
|
62
|
+
else
|
63
|
+
return nil
|
64
|
+
end
|
60
65
|
end
|
66
|
+
stage
|
61
67
|
end
|
62
|
-
stage
|
63
|
-
end
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
def remove(sig)
|
70
|
+
stage = self
|
71
|
+
s2 = sig.slice(0..-2)
|
72
|
+
s2.each do |a|
|
73
|
+
if stage.has_key?(a)
|
74
|
+
stage = stage[a]
|
75
|
+
else
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
stage.delete(sig.last)
|
68
80
|
end
|
69
|
-
self.values_at(*next_keys)
|
70
|
-
end
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
82
|
+
def children(matcher=true)
|
83
|
+
next_keys = self.keys.select do |key|
|
84
|
+
match?(matcher, key)
|
85
|
+
end
|
86
|
+
self.values_at(*next_keys)
|
87
|
+
end
|
77
88
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
+
def +(other)
|
90
|
+
out = HashTree.new
|
91
|
+
_plus(other, out)
|
92
|
+
out
|
93
|
+
end
|
94
|
+
|
95
|
+
def _plus(ht2, out)
|
96
|
+
self.each do |k1,v1|
|
97
|
+
v1 = v1.respond_to?(:dup) ? v1 : v1.dup
|
98
|
+
if ht2.has_key?(k1)
|
99
|
+
v2 = ht2[k1]
|
100
|
+
if v1.respond_to?(:_plus)
|
101
|
+
out[k1] = v1
|
102
|
+
v1._plus(v2, out[k1])
|
103
|
+
elsif v2.respond_to?(:_plus)
|
104
|
+
raise ArgumentError,
|
105
|
+
"Can't merge leaf with non-leaf:\n#{v1.inspect}\n#{v2.inspect}"
|
106
|
+
else
|
107
|
+
if v2.is_a?(Numeric) && v1.is_a?(Numeric)
|
108
|
+
out[k1] = v1 + v2
|
109
|
+
else
|
110
|
+
out[k1] = [v1, ht2[k1]]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
# should anything happen here?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
ht2.each do |k,v|
|
118
|
+
if self.has_key?(k)
|
119
|
+
# should anything happen here?
|
120
|
+
else
|
121
|
+
v = v.respond_to?(:dup) ? v : v.dup
|
122
|
+
out[k] = v
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Merge the argument into self, overwriting where necessary.
|
128
|
+
def <<(other)
|
129
|
+
other.each do |k,v1|
|
130
|
+
if self.has_key?(k)
|
131
|
+
v2 = self[k]
|
132
|
+
if v1.respond_to?(:has_key?) && v2.respond_to?(:has_key?)
|
133
|
+
v2 << v1
|
134
|
+
elsif v1.is_a?(Numeric) && v2.is_a?(Numeric)
|
135
|
+
self[k] = v1 + v2
|
136
|
+
else
|
137
|
+
raise ArgumentError,
|
138
|
+
"Can't merge leaf with non-leaf:\n#{v1.inspect}\n#{v2.inspect}"
|
139
|
+
end
|
89
140
|
else
|
90
|
-
if
|
91
|
-
|
141
|
+
if v1.respond_to?(:has_key?)
|
142
|
+
self[k] << v1
|
92
143
|
else
|
93
|
-
|
144
|
+
self[k] = v1
|
94
145
|
end
|
95
146
|
end
|
96
|
-
else
|
97
|
-
# should anything happen here?
|
98
147
|
end
|
99
148
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
149
|
+
|
150
|
+
def match?(val, key)
|
151
|
+
case val
|
152
|
+
when true
|
153
|
+
true
|
154
|
+
when String, Symbol
|
155
|
+
key == val
|
156
|
+
when Regexp
|
157
|
+
key =~ val
|
158
|
+
when Proc
|
159
|
+
val.call(key)
|
160
|
+
when nil
|
161
|
+
false
|
103
162
|
else
|
104
|
-
|
105
|
-
out[k] = v
|
163
|
+
raise ArgumentError, "Unexpected matcher type: #{val.inspect}"
|
106
164
|
end
|
107
165
|
end
|
108
|
-
end
|
109
166
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
if
|
115
|
-
|
116
|
-
elsif v1.is_a?(Numeric) && v2.is_a?(Numeric)
|
117
|
-
self[k] = v1 + v2
|
167
|
+
# Depth-first traversal, yielding the path to each leaf.
|
168
|
+
def each_path(stack=[], &block)
|
169
|
+
self.each do |k, v|
|
170
|
+
stack.push(k)
|
171
|
+
if v.respond_to?(:each_path)
|
172
|
+
v.each_path(stack, &block)
|
118
173
|
else
|
119
|
-
|
120
|
-
|
174
|
+
yield(stack, v)
|
175
|
+
#block.call(stack, v)
|
121
176
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
177
|
+
stack.pop
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def each_leaf(stack=[], &block)
|
182
|
+
self.each do |k,v|
|
183
|
+
stack.push(k)
|
184
|
+
if v.respond_to?(:each_leaf)
|
185
|
+
v.each_leaf(stack, &block)
|
125
186
|
else
|
126
|
-
|
187
|
+
yield(v)
|
188
|
+
#block.call(v)
|
127
189
|
end
|
190
|
+
stack.pop
|
128
191
|
end
|
129
192
|
end
|
130
|
-
end
|
131
193
|
|
132
|
-
def match?(val, key)
|
133
|
-
case val
|
134
|
-
when true
|
135
|
-
true
|
136
|
-
when String, Symbol
|
137
|
-
key == val
|
138
|
-
when Regexp
|
139
|
-
key =~ val
|
140
|
-
when Proc
|
141
|
-
val.call(key)
|
142
|
-
when nil
|
143
|
-
false
|
144
|
-
else
|
145
|
-
raise ArgumentError, "Unexpected matcher type: #{val.inspect}"
|
146
|
-
end
|
147
194
|
end
|
148
195
|
|
149
196
|
end
|
150
197
|
|
151
|
-
|
152
|
-
|
153
|
-
|
metadata
CHANGED