hashery 1.5.0 → 2.0.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.
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