hashery 1.5.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +30 -17
- data/.yardopts +1 -0
- data/Config.rb +28 -0
- data/{QED.rdoc → DEMO.rdoc} +0 -0
- data/HISTORY.rdoc +37 -0
- data/LICENSE.txt +26 -0
- data/NOTICE.txt +46 -0
- data/README.rdoc +10 -7
- data/lib/hashery.rb +6 -6
- data/lib/hashery.yml +30 -17
- data/lib/hashery/association.rb +169 -109
- data/lib/hashery/casting_hash.rb +128 -135
- data/lib/hashery/core_ext.rb +89 -61
- data/lib/hashery/crud_hash.rb +365 -0
- data/lib/hashery/dictionary.rb +545 -345
- data/lib/hashery/fuzzy_hash.rb +177 -125
- data/lib/hashery/ini_hash.rb +321 -0
- data/lib/hashery/key_hash.rb +54 -179
- data/lib/hashery/linked_list.rb +245 -191
- data/lib/hashery/lru_hash.rb +292 -202
- data/lib/hashery/open_cascade.rb +133 -78
- data/lib/hashery/open_hash.rb +127 -61
- data/lib/hashery/ordered_hash.rb +128 -122
- data/lib/hashery/path_hash.rb +238 -0
- data/lib/hashery/property_hash.rb +144 -80
- data/lib/hashery/query_hash.rb +85 -29
- data/lib/hashery/stash.rb +7 -3
- data/lib/hashery/static_hash.rb +46 -41
- data/test/case_association.rb +65 -4
- data/test/case_dictionary.rb +149 -5
- data/test/{case_keyhash.rb → case_key_hash.rb} +20 -14
- data/test/case_lru_hash.rb +162 -0
- data/test/{case_opencascade.rb → case_open_cascade.rb} +4 -8
- data/test/case_open_hash.rb +87 -0
- data/test/case_query_hash.rb +226 -0
- data/test/helper.rb +8 -0
- metadata +33 -63
- data/COPYING.rdoc +0 -45
- data/lib/hashery/basic_object.rb +0 -74
- data/lib/hashery/basic_struct.rb +0 -288
- data/lib/hashery/basicobject.rb +0 -1
- data/lib/hashery/basicstruct.rb +0 -1
- data/lib/hashery/castinghash.rb +0 -1
- data/lib/hashery/fuzzyhash.rb +0 -1
- data/lib/hashery/ini.rb +0 -268
- data/lib/hashery/keyhash.rb +0 -1
- data/lib/hashery/linkedlist.rb +0 -1
- data/lib/hashery/lruhash.rb +0 -1
- data/lib/hashery/memoizer.rb +0 -64
- data/lib/hashery/open_object.rb +0 -1
- data/lib/hashery/opencascade.rb +0 -1
- data/lib/hashery/openhash.rb +0 -1
- data/lib/hashery/openobject.rb +0 -1
- data/lib/hashery/orderedhash.rb +0 -1
- data/lib/hashery/ostructable.rb +0 -186
- data/lib/hashery/propertyhash.rb +0 -1
- data/lib/hashery/queryhash.rb +0 -1
- data/lib/hashery/statichash.rb +0 -1
- data/qed/01_openhash.rdoc +0 -57
- data/qed/02_queryhash.rdoc +0 -21
- data/qed/03_castinghash.rdoc +0 -13
- data/qed/04_statichash.rdoc +0 -22
- data/qed/05_association.rdoc +0 -59
- data/qed/06_opencascade.rdoc +0 -58
- data/qed/07_fuzzyhash.rdoc +0 -141
- data/qed/08_properyhash.rdoc +0 -38
- data/qed/09_ostructable.rdoc +0 -56
- data/qed/applique/ae.rb +0 -1
- data/test/case_basicstruct.rb +0 -192
- data/test/case_openhash.rb +0 -22
@@ -0,0 +1,238 @@
|
|
1
|
+
module Hashery
|
2
|
+
|
3
|
+
# A PathHash is a hash whose values can be accessed in the normal manner,
|
4
|
+
# or with keys that are slash (`/`) separated strings. To get the whole hash
|
5
|
+
# as a single flattened level, call `#flat`. All keys are converted to strings.
|
6
|
+
# All end-of-the-chain values are kept in whatever value they are.
|
7
|
+
#
|
8
|
+
# s = PathHash['a' => 'b', 'c' => {'d' => :e}]
|
9
|
+
# s['a'] #=> 'b'
|
10
|
+
# s['c'] #=> {slashed: 'd'=>:e}
|
11
|
+
# s['c']['d'] #=> :e
|
12
|
+
# s['c/d'] #=> :e
|
13
|
+
#
|
14
|
+
# PathHash is derived from the SlashedHash class in the HashMagic project
|
15
|
+
# by Daniel Parker <gems@behindlogic.com>.
|
16
|
+
#
|
17
|
+
# Copyright (c) 2006 BehindLogic (http://hash_magic.rubyforge.org)
|
18
|
+
#
|
19
|
+
# Authors: Daniel Parker
|
20
|
+
#
|
21
|
+
# TODO: This class is very much a work in progess and will be substantially rewritten
|
22
|
+
# for future versions.
|
23
|
+
#
|
24
|
+
class PathHash < Hash
|
25
|
+
|
26
|
+
#
|
27
|
+
# Initialize PathHash.
|
28
|
+
#
|
29
|
+
# hsh - Priming Hash.
|
30
|
+
#
|
31
|
+
def initialize(hsh={})
|
32
|
+
raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array)
|
33
|
+
@constructor = hsh.is_a?(Hash) ? hsh.class : Hash
|
34
|
+
@flat = flatten_to_hash(hsh)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Standard Hash methods, plus the overwritten ones
|
38
|
+
#include StandardHashMethodsInRuby
|
39
|
+
|
40
|
+
# Behaves like the usual Hash#[] method, but you can access nested hash
|
41
|
+
# values by composing a single key of the traversing keys joined by '/':
|
42
|
+
#
|
43
|
+
# hash['c']['d'] # is the same as:
|
44
|
+
# hash['c/d']
|
45
|
+
#
|
46
|
+
def [](key)
|
47
|
+
rg = Regexp.new("^#{key}/?")
|
48
|
+
start_obj = if @constructor == OrderedHash
|
49
|
+
@constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')})
|
50
|
+
else
|
51
|
+
@constructor.new
|
52
|
+
end
|
53
|
+
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})
|
54
|
+
v.is_a?(self.class) && v.empty? ? nil : v
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Same as above, except sets value rather than retrieving it.
|
59
|
+
#
|
60
|
+
def []=(key,value)
|
61
|
+
@flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")}
|
62
|
+
if value.is_a?(Hash)
|
63
|
+
flatten_to_hash(value).each do |hk,hv|
|
64
|
+
@flat[key.to_s+'/'+hk.to_s] = hv
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@flat[key.to_s] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear # :nodoc:
|
72
|
+
@flat.clear
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
#
|
77
|
+
#
|
78
|
+
def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc:
|
79
|
+
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})
|
80
|
+
if value.is_a?(self.class) && value.empty?
|
81
|
+
if default == :ehisehoah0928309q98y30
|
82
|
+
if block_given?
|
83
|
+
block.call(key)
|
84
|
+
else
|
85
|
+
raise IndexError
|
86
|
+
end
|
87
|
+
value
|
88
|
+
else
|
89
|
+
default
|
90
|
+
end
|
91
|
+
else
|
92
|
+
value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Delete entry from Hash. Slashed keys can be used here, too.
|
98
|
+
#
|
99
|
+
# key - The key to delete.
|
100
|
+
# block - Produces the return value if key not found.
|
101
|
+
#
|
102
|
+
# Returns delete value.
|
103
|
+
#
|
104
|
+
def delete(key,&block)
|
105
|
+
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})
|
106
|
+
return block.call(key) if value.is_a?(self.class) && value.empty? && block_given?
|
107
|
+
@flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)}
|
108
|
+
return value
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
def empty?
|
113
|
+
@flat.empty?
|
114
|
+
end
|
115
|
+
|
116
|
+
# This gives you the slashed key of the value, no matter where the value is in the tree.
|
117
|
+
def index(value)
|
118
|
+
@flat.index(value)
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
def inspect
|
123
|
+
@flat.inspect.insert(1,'slashed: ')
|
124
|
+
end
|
125
|
+
|
126
|
+
# This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys
|
127
|
+
def keys
|
128
|
+
@flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq
|
129
|
+
end
|
130
|
+
|
131
|
+
# This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you
|
132
|
+
# end up with an array holding several hashes.
|
133
|
+
def rehash # :nodoc:
|
134
|
+
@flat.rehash
|
135
|
+
end
|
136
|
+
|
137
|
+
# Gives a list of all keys in all levels in the multi-level hash, joined by slashes.
|
138
|
+
#
|
139
|
+
# {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys
|
140
|
+
# #=> ['a/b', 'a/c', 'b']
|
141
|
+
#
|
142
|
+
def flat
|
143
|
+
@flat
|
144
|
+
end
|
145
|
+
|
146
|
+
# Expands the whole hash to Hash objects ... not useful very often, it seems.
|
147
|
+
def expand
|
148
|
+
inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h}
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_string_array
|
152
|
+
flatten_to_array(flat,[])
|
153
|
+
end
|
154
|
+
|
155
|
+
def slashed # :nodoc:
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
# Same as ordered! but returns a new SlashedHash object instead of modifying the same.
|
160
|
+
def ordered(*keys_in_order)
|
161
|
+
dup.ordered!(*keys_in_order)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Sets the SlashedArray as ordered. The *keys_in_order must be a flat array
|
165
|
+
# of slashed keys that specify the order for each level:
|
166
|
+
#
|
167
|
+
# s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed
|
168
|
+
# s.ordered!('b', 'a/c', 'a/b')
|
169
|
+
# s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}}
|
170
|
+
# # Note that the expanded hashes will *still* be ordered!
|
171
|
+
#
|
172
|
+
def ordered!(*keys_in_order)
|
173
|
+
return self if @constructor == OrderedHash
|
174
|
+
@constructor = OrderedHash
|
175
|
+
@flat = @flat.ordered(*keys_in_order)
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
def ==(other)
|
181
|
+
case other
|
182
|
+
when SlashedHash
|
183
|
+
@slashed == other.instance_variable_get(:@slashed)
|
184
|
+
when Hash
|
185
|
+
self == SlashedHash.new(other)
|
186
|
+
else
|
187
|
+
raise TypeError, "Cannot compare #{other.class.name} with SlashedHash"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def flatten_to_hash(hsh)
|
194
|
+
flat = @constructor.new
|
195
|
+
if hsh.is_a?(Array)
|
196
|
+
hsh.each do |e|
|
197
|
+
flat.merge!(flatten_to_hash(e))
|
198
|
+
end
|
199
|
+
elsif hsh.is_a?(Hash)
|
200
|
+
hsh.each do |k,v|
|
201
|
+
if v.is_a?(Hash)
|
202
|
+
flatten_to_hash(v).each do |hk,hv|
|
203
|
+
flat[k.to_s+'/'+hk.to_s] = hv
|
204
|
+
end
|
205
|
+
else
|
206
|
+
flat[k.to_s] = v
|
207
|
+
end
|
208
|
+
end
|
209
|
+
else
|
210
|
+
ks = hsh.split('/',-1)
|
211
|
+
v = ks.pop
|
212
|
+
ks = ks.join('/')
|
213
|
+
if !flat[ks].nil?
|
214
|
+
if flat[ks].is_a?(Array)
|
215
|
+
flat[ks] << v
|
216
|
+
else
|
217
|
+
flat[ks] = [flat[ks], v]
|
218
|
+
end
|
219
|
+
else
|
220
|
+
flat[ks] = v
|
221
|
+
end
|
222
|
+
end
|
223
|
+
flat
|
224
|
+
end
|
225
|
+
|
226
|
+
def flatten_to_array(value,a)
|
227
|
+
if value.is_a?(Array)
|
228
|
+
value.each {|e| flatten_to_array(e,a)}
|
229
|
+
elsif value.is_a?(Hash)
|
230
|
+
value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e}
|
231
|
+
else
|
232
|
+
a << value.to_s
|
233
|
+
end
|
234
|
+
a
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
@@ -1,97 +1,161 @@
|
|
1
|
-
|
2
|
-
# allowed keys.
|
3
|
-
#
|
4
|
-
# There are two ways to use it.
|
5
|
-
#
|
6
|
-
# 1) As an object in itself.
|
7
|
-
#
|
8
|
-
# h = PropertyHash.new(:a=>1, :b=>2)
|
9
|
-
# h[:a] #=> 1
|
10
|
-
# h[:a] = 3
|
11
|
-
# h[:a] #=> 3
|
12
|
-
#
|
13
|
-
# But if we try to set key that was not fixed, then we will get an error.
|
14
|
-
#
|
15
|
-
# h[:x] = 5 #=> ArgumentError
|
16
|
-
#
|
17
|
-
# 2) As a superclass.
|
18
|
-
#
|
19
|
-
# class MyPropertyHash < PropertyHash
|
20
|
-
# property :a, :default => 1
|
21
|
-
# property :b, :default => 2
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# h = MyPropertyHash.new
|
25
|
-
# h[:a] #=> 1
|
26
|
-
# h[:a] = 3
|
27
|
-
# h[:a] #=> 3
|
28
|
-
#
|
29
|
-
# Again, if we try to set key that was not fixed, then we will get an error.
|
30
|
-
#
|
31
|
-
# h[:x] = 5 #=> ArgumentError
|
32
|
-
#
|
33
|
-
class PropertyHash < Hash
|
1
|
+
require 'hashery/crud_hash'
|
34
2
|
|
3
|
+
module Hashery
|
4
|
+
|
5
|
+
# A PropertyHash is the same as a regular Hash except it strictly limits the
|
6
|
+
# allowed keys.
|
7
|
+
#
|
8
|
+
# There are two ways to use it.
|
9
|
+
#
|
10
|
+
# 1) As an object in itself.
|
11
|
+
#
|
12
|
+
# h = PropertyHash.new(:a=>1, :b=>2)
|
13
|
+
# h[:a] #=> 1
|
14
|
+
# h[:a] = 3
|
15
|
+
# h[:a] #=> 3
|
16
|
+
#
|
17
|
+
# But if we try to set key that was not fixed, then we will get an error.
|
18
|
+
#
|
19
|
+
# h[:x] = 5 #=> ArgumentError
|
20
|
+
#
|
21
|
+
# 2) As a superclass.
|
22
|
+
#
|
23
|
+
# class MyPropertyHash < PropertyHash
|
24
|
+
# property :a, :default => 1
|
25
|
+
# property :b, :default => 2
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# h = MyPropertyHash.new
|
29
|
+
# h[:a] #=> 1
|
30
|
+
# h[:a] = 3
|
31
|
+
# h[:a] #=> 3
|
32
|
+
#
|
33
|
+
# Again, if we try to set key that was not fixed, then we will get an error.
|
34
|
+
#
|
35
|
+
# h[:x] = 5 #=> ArgumentError
|
35
36
|
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
class PropertyHash < CRUDHash
|
38
|
+
|
39
|
+
#
|
40
|
+
# Get a list of properties with default values.
|
41
|
+
#
|
42
|
+
# Returns [Hash] of properties and their default values.
|
43
|
+
#
|
44
|
+
def self.properties
|
45
|
+
@properties ||= (
|
46
|
+
parent = ancestors[1]
|
47
|
+
if parent.respond_to?(:properties)
|
48
|
+
parent.properties
|
49
|
+
else
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Define a property.
|
57
|
+
#
|
58
|
+
# key - Name of property.
|
59
|
+
# opts - Property options.
|
60
|
+
# :default - Default value of property.
|
61
|
+
#
|
62
|
+
# Returns default value.
|
63
|
+
#
|
64
|
+
def self.property(key, opts={})
|
65
|
+
properties[key] = opts[:default]
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Initialize new instance of PropertyHash.
|
70
|
+
#
|
71
|
+
# properties - [Hash] Priming properties with default values, or
|
72
|
+
# if it doesn't respond to #each_pair, a default object.
|
73
|
+
# default_proc - [Proc] Procedure for default value of properties
|
74
|
+
# for properties without specific defaults.
|
75
|
+
#
|
76
|
+
def initialize(properties={}, &default_proc)
|
77
|
+
if properties.respond_to?(:each_pair)
|
78
|
+
super(&default_proc)
|
79
|
+
fixed = self.class.properties.merge(properties)
|
80
|
+
fixed.each_pair do |key, value|
|
81
|
+
store!(key, value)
|
82
|
+
end
|
41
83
|
else
|
42
|
-
|
84
|
+
super(*[properties].compact, &default_proc)
|
43
85
|
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
#
|
48
|
-
def self.property(key, opts={})
|
49
|
-
properties[key] = opts[:default]
|
50
|
-
end
|
86
|
+
end
|
51
87
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
fixed = self.class.properties.merge(properties)
|
56
|
-
replace(fixed)
|
57
|
-
end
|
88
|
+
# Alias original #store method and make private.
|
89
|
+
alias :store! :store
|
90
|
+
private :store!
|
58
91
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
92
|
+
#
|
93
|
+
# Create a new property, on-the-fly.
|
94
|
+
#
|
95
|
+
# key - Name of property.
|
96
|
+
# opts - Property options.
|
97
|
+
# :default - Default value of property.
|
98
|
+
#
|
99
|
+
# Returns default value.
|
100
|
+
#
|
101
|
+
def property(key, opts={})
|
102
|
+
if opts[:default]
|
103
|
+
store!(key, opts[:default])
|
104
|
+
else
|
105
|
+
store!(key, read(key))
|
106
|
+
end
|
107
|
+
end
|
64
108
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
109
|
+
#
|
110
|
+
# Store key value pair, ensuring the key is a valid property first.
|
111
|
+
#
|
112
|
+
# key - The `Object` to act as indexing key.
|
113
|
+
# value - The `Object` to associate with key.
|
114
|
+
#
|
115
|
+
# Raises ArgumentError if key is not a valid property.
|
116
|
+
#
|
117
|
+
# Returns +value+.
|
118
|
+
#
|
119
|
+
def store(key, value)
|
120
|
+
assert_key!(key)
|
121
|
+
super(key, value)
|
122
|
+
end
|
70
123
|
|
71
|
-
|
72
|
-
|
73
|
-
h.keys.each{ |k| assert_key!(k) }
|
74
|
-
super(h)
|
75
|
-
|
124
|
+
#
|
125
|
+
#def update(h)
|
126
|
+
# h.keys.each{ |k| assert_key!(k) }
|
127
|
+
# super(h)
|
128
|
+
#end
|
76
129
|
|
77
|
-
|
78
|
-
|
79
|
-
k
|
80
|
-
|
81
|
-
|
130
|
+
#
|
131
|
+
#def merge!(h)
|
132
|
+
# h.keys.each{ |k| assert_key!(k) }
|
133
|
+
# super(h)
|
134
|
+
#end
|
82
135
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
136
|
+
#
|
137
|
+
# Like #store but takes a two-element Array of `[key, value]`.
|
138
|
+
#
|
139
|
+
# Returns value.
|
140
|
+
#
|
141
|
+
#def <<(a)
|
142
|
+
# k,v = *a
|
143
|
+
# store(k,v)
|
144
|
+
#end
|
88
145
|
|
89
146
|
private
|
90
147
|
|
91
|
-
|
92
|
-
|
93
|
-
|
148
|
+
#
|
149
|
+
# Asserta that a key is a defined property.
|
150
|
+
#
|
151
|
+
# Raises ArgumentError if key is not a property.
|
152
|
+
#
|
153
|
+
def assert_key!(key)
|
154
|
+
unless key?(key)
|
155
|
+
raise ArgumentError, "property is not defined -- #{key.inspect}"
|
156
|
+
end
|
94
157
|
end
|
158
|
+
|
95
159
|
end
|
96
160
|
|
97
161
|
end
|