hashery 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.ruby +30 -17
  2. data/.yardopts +1 -0
  3. data/Config.rb +28 -0
  4. data/{QED.rdoc → DEMO.rdoc} +0 -0
  5. data/HISTORY.rdoc +37 -0
  6. data/LICENSE.txt +26 -0
  7. data/NOTICE.txt +46 -0
  8. data/README.rdoc +10 -7
  9. data/lib/hashery.rb +6 -6
  10. data/lib/hashery.yml +30 -17
  11. data/lib/hashery/association.rb +169 -109
  12. data/lib/hashery/casting_hash.rb +128 -135
  13. data/lib/hashery/core_ext.rb +89 -61
  14. data/lib/hashery/crud_hash.rb +365 -0
  15. data/lib/hashery/dictionary.rb +545 -345
  16. data/lib/hashery/fuzzy_hash.rb +177 -125
  17. data/lib/hashery/ini_hash.rb +321 -0
  18. data/lib/hashery/key_hash.rb +54 -179
  19. data/lib/hashery/linked_list.rb +245 -191
  20. data/lib/hashery/lru_hash.rb +292 -202
  21. data/lib/hashery/open_cascade.rb +133 -78
  22. data/lib/hashery/open_hash.rb +127 -61
  23. data/lib/hashery/ordered_hash.rb +128 -122
  24. data/lib/hashery/path_hash.rb +238 -0
  25. data/lib/hashery/property_hash.rb +144 -80
  26. data/lib/hashery/query_hash.rb +85 -29
  27. data/lib/hashery/stash.rb +7 -3
  28. data/lib/hashery/static_hash.rb +46 -41
  29. data/test/case_association.rb +65 -4
  30. data/test/case_dictionary.rb +149 -5
  31. data/test/{case_keyhash.rb → case_key_hash.rb} +20 -14
  32. data/test/case_lru_hash.rb +162 -0
  33. data/test/{case_opencascade.rb → case_open_cascade.rb} +4 -8
  34. data/test/case_open_hash.rb +87 -0
  35. data/test/case_query_hash.rb +226 -0
  36. data/test/helper.rb +8 -0
  37. metadata +33 -63
  38. data/COPYING.rdoc +0 -45
  39. data/lib/hashery/basic_object.rb +0 -74
  40. data/lib/hashery/basic_struct.rb +0 -288
  41. data/lib/hashery/basicobject.rb +0 -1
  42. data/lib/hashery/basicstruct.rb +0 -1
  43. data/lib/hashery/castinghash.rb +0 -1
  44. data/lib/hashery/fuzzyhash.rb +0 -1
  45. data/lib/hashery/ini.rb +0 -268
  46. data/lib/hashery/keyhash.rb +0 -1
  47. data/lib/hashery/linkedlist.rb +0 -1
  48. data/lib/hashery/lruhash.rb +0 -1
  49. data/lib/hashery/memoizer.rb +0 -64
  50. data/lib/hashery/open_object.rb +0 -1
  51. data/lib/hashery/opencascade.rb +0 -1
  52. data/lib/hashery/openhash.rb +0 -1
  53. data/lib/hashery/openobject.rb +0 -1
  54. data/lib/hashery/orderedhash.rb +0 -1
  55. data/lib/hashery/ostructable.rb +0 -186
  56. data/lib/hashery/propertyhash.rb +0 -1
  57. data/lib/hashery/queryhash.rb +0 -1
  58. data/lib/hashery/statichash.rb +0 -1
  59. data/qed/01_openhash.rdoc +0 -57
  60. data/qed/02_queryhash.rdoc +0 -21
  61. data/qed/03_castinghash.rdoc +0 -13
  62. data/qed/04_statichash.rdoc +0 -22
  63. data/qed/05_association.rdoc +0 -59
  64. data/qed/06_opencascade.rdoc +0 -58
  65. data/qed/07_fuzzyhash.rdoc +0 -141
  66. data/qed/08_properyhash.rdoc +0 -38
  67. data/qed/09_ostructable.rdoc +0 -56
  68. data/qed/applique/ae.rb +0 -1
  69. data/test/case_basicstruct.rb +0 -192
  70. 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
- # A PropertyHash is the same as a regular Hash except it strictly limits the
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
- def self.properties
37
- @properties ||= (
38
- parent = ancestors[1]
39
- if parent.respond_to?(:properties)
40
- parent.properties
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
- def initialize(properties={})
54
- super()
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
- def []=(k,v)
61
- assert_key!(k)
62
- super(k,v)
63
- end
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
- def update(h)
67
- h.keys.each{ |k| assert_key!(k) }
68
- super(h)
69
- end
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
- def merge!(h)
73
- h.keys.each{ |k| assert_key!(k) }
74
- super(h)
75
- end
124
+ #
125
+ #def update(h)
126
+ # h.keys.each{ |k| assert_key!(k) }
127
+ # super(h)
128
+ #end
76
129
 
77
- #
78
- def <<(a)
79
- k,v = *a
80
- self[k] = v
81
- end
130
+ #
131
+ #def merge!(h)
132
+ # h.keys.each{ |k| assert_key!(k) }
133
+ # super(h)
134
+ #end
82
135
 
83
- # Add a new acceptable key.
84
- # TODO: Should this be supported?
85
- def accept_key!(k,v=nil)
86
- self[k] = v
87
- end
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
- def assert_key!(key)
92
- unless key?(key)
93
- raise ArgumentError, "The key '#{key}' is not defined for this FixedHash."
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