hashery 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +98 -0
- data/HISTORY.rdoc +17 -4
- data/NOTICE +11 -0
- data/lib/hashery.rb +1 -1
- data/lib/hashery.yml +98 -0
- data/lib/hashery/basic_struct.rb +1 -0
- data/lib/hashery/basicobject.rb +74 -0
- data/lib/hashery/basicstruct.rb +280 -0
- data/lib/hashery/open_object.rb +1 -1
- data/lib/hashery/openobject.rb +1 -279
- data/lib/hashery/ostructable.rb +48 -71
- data/qed/01_openhash.rdoc +57 -0
- data/qed/02_queryhash.rdoc +21 -0
- data/qed/03_castinghash.rdoc +13 -0
- data/qed/04_statichash.rdoc +22 -0
- data/qed/05_association.rdoc +59 -0
- data/qed/06_opencascade.rdoc +58 -0
- data/qed/07_fuzzyhash.rdoc +141 -0
- data/qed/08_properyhash.rdoc +38 -0
- data/qed/09_ostructable.rdoc +56 -0
- data/qed/applique/ae.rb +1 -0
- metadata +53 -9
- data/PROFILE +0 -35
- data/VERSION +0 -3
@@ -0,0 +1,57 @@
|
|
1
|
+
= OpenHash
|
2
|
+
|
3
|
+
An OpenHash is a Hash that provides +open+ access to its entries via method
|
4
|
+
calls. Writers (methods ending in =-marks) assign entries. Methods without
|
5
|
+
special puncuation will retrieve entries.
|
6
|
+
|
7
|
+
require 'hashery/openhash'
|
8
|
+
|
9
|
+
o = OpenHash.new
|
10
|
+
o.a = 1
|
11
|
+
o.b = 2
|
12
|
+
o.a.assert == 1
|
13
|
+
o.b.assert == 2
|
14
|
+
|
15
|
+
Writers always use a Symbol for keys in the underlying Hash.
|
16
|
+
|
17
|
+
o.to_h.assert == { :a=>1, :b=>2 }
|
18
|
+
|
19
|
+
All the usual Hash methods are still available in an OpenHash.
|
20
|
+
|
21
|
+
o.map{ |k,v| [k,v] }
|
22
|
+
|
23
|
+
And they are protected from being overridden by writers.
|
24
|
+
|
25
|
+
o.map = 3
|
26
|
+
o.map{ |k,v| [k,v] }
|
27
|
+
|
28
|
+
Even so, the underlying Hash object does contain the entry even
|
29
|
+
when it can not be accessed via a reader method.
|
30
|
+
|
31
|
+
o.to_h.assert == {:a=>1, :b=>2, :map=>3 }
|
32
|
+
|
33
|
+
For some usecases it may be necessary to give up access to one or
|
34
|
+
more Hash methods in favor of access to the hash entries. This can
|
35
|
+
be done using the #omit! method.
|
36
|
+
|
37
|
+
o.omit!(:map, :merge)
|
38
|
+
o.map.assert == 3
|
39
|
+
o.merge = 4
|
40
|
+
o.merge.assert == 4
|
41
|
+
|
42
|
+
Becuase of nature of a writer, a certain set of Hash methods are always
|
43
|
+
protected, namely any of those ending in a puctuation mark. In addition,
|
44
|
+
the implementation protects all shadow methods (e.g. __id__).
|
45
|
+
|
46
|
+
o.__id__ = 4
|
47
|
+
o.__id__.assert! == 4
|
48
|
+
|
49
|
+
Even though writers alwasy use Symbols as keys, because an OpenHash
|
50
|
+
is a true Hash object, any object can be used as a key internally.
|
51
|
+
|
52
|
+
o = OpenHash.new
|
53
|
+
o[nil] = "Nothing"
|
54
|
+
o.to_h.assert == { nil=>"Nothing" }
|
55
|
+
|
56
|
+
It simply will not be accessible via a reader method.
|
57
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= QueryHash
|
2
|
+
|
3
|
+
A QueryHash is a Hash that provides open access much like
|
4
|
+
an OpenHash, but it limits readers to query methods (i.e.
|
5
|
+
method ending in ?-marks).
|
6
|
+
|
7
|
+
require 'hashery/queryhash'
|
8
|
+
|
9
|
+
q = QueryHash.new
|
10
|
+
q.a = 1
|
11
|
+
q.b = 2
|
12
|
+
q.a?.assert == 1
|
13
|
+
q.b?.assert == 2
|
14
|
+
|
15
|
+
Writers always use Symbols for entry keys.
|
16
|
+
|
17
|
+
q.assert == { :a=>1, :b=>2 }
|
18
|
+
|
19
|
+
A QueryHash is compatible with Ruby's standard Hash in
|
20
|
+
every other respect.
|
21
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
= CastingHash
|
2
|
+
|
3
|
+
A CastingHash is a Hash that allows _casting_ procedures to
|
4
|
+
defined that the keys and values pass through upon assignment.
|
5
|
+
|
6
|
+
require 'hashery/castinghash'
|
7
|
+
|
8
|
+
c = CastingHash.new{ |x| x.to_s }
|
9
|
+
|
10
|
+
c[:a] = 1
|
11
|
+
|
12
|
+
c.assert == {'a'=>1}
|
13
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
= StaticHash
|
2
|
+
|
3
|
+
A StaticHash is simply a Hash that can only be assigned
|
4
|
+
once per key. Once assigned a subsequent attempt to assign
|
5
|
+
a value to the same key will raise an ArgumentError.
|
6
|
+
|
7
|
+
require 'hashery/statichash'
|
8
|
+
|
9
|
+
h = StaticHash.new
|
10
|
+
|
11
|
+
h["x"] = 1
|
12
|
+
|
13
|
+
expect ArgumentError do
|
14
|
+
h["x"] = 2
|
15
|
+
end
|
16
|
+
|
17
|
+
The same error will be raised when using #update or #merge!.
|
18
|
+
|
19
|
+
expect ArgumentError do
|
20
|
+
h.update( :x=>2 )
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
= Association
|
2
|
+
|
3
|
+
An Association is a class for creating simple pairings.
|
4
|
+
|
5
|
+
require 'hashery/association'
|
6
|
+
|
7
|
+
An Association can bew created through the usual means
|
8
|
+
of instantiation.
|
9
|
+
|
10
|
+
Association.new(:a, :b)
|
11
|
+
|
12
|
+
Or the shortcut method #>> can be used in most cases.
|
13
|
+
|
14
|
+
:x >> :z
|
15
|
+
|
16
|
+
An association provides two methods to access its content, #index and #value.
|
17
|
+
|
18
|
+
a = 'foo' >> 'bar'
|
19
|
+
|
20
|
+
a.index.assert == 'foo'
|
21
|
+
a.value.assert == 'bar'
|
22
|
+
|
23
|
+
Associations can be used to create ordered-hashes via normal
|
24
|
+
arrays.
|
25
|
+
|
26
|
+
keys = []
|
27
|
+
vals = []
|
28
|
+
|
29
|
+
ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
|
30
|
+
|
31
|
+
ohash.each{ |k,v| keys << k ; vals << v }
|
32
|
+
|
33
|
+
keys.assert == ['A','B','C']
|
34
|
+
vals.assert == ['3','2','1']
|
35
|
+
|
36
|
+
|
37
|
+
Becuase Associations are objects in themselves more complex
|
38
|
+
collections can also be created.
|
39
|
+
|
40
|
+
complex = [
|
41
|
+
'parent' >> 'child',
|
42
|
+
'childless',
|
43
|
+
'another_parent' >> [
|
44
|
+
'subchildless',
|
45
|
+
'subparent' >> 'subchild'
|
46
|
+
]
|
47
|
+
]
|
48
|
+
|
49
|
+
An experimental feature of Association keeps a cache of all defined associations.
|
50
|
+
|
51
|
+
o = Object.new
|
52
|
+
o >> :a
|
53
|
+
o >> :b
|
54
|
+
o >> :c
|
55
|
+
|
56
|
+
o.associations.assert == [:a, :b, :c]
|
57
|
+
|
58
|
+
However this feature will probably be deprecated.
|
59
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
= OpenCascade
|
2
|
+
|
3
|
+
OpenCascade is subclass of OpenObject. It differs in a few
|
4
|
+
significant ways.
|
5
|
+
|
6
|
+
require 'hashery/opencascade'
|
7
|
+
|
8
|
+
The main reason this class is labeled "cascade", every internal
|
9
|
+
Hash is transformed into an OpenCascade dynamically upon access.
|
10
|
+
This makes it easy to create "cascading" references.
|
11
|
+
|
12
|
+
h = { :x => { :y => { :z => 1 } } }
|
13
|
+
c = OpenCascade[h]
|
14
|
+
c.x.y.z.assert == 1
|
15
|
+
|
16
|
+
As soon as you access a node it automatically becomes an OpenCascade.
|
17
|
+
|
18
|
+
c = OpenCascade.new
|
19
|
+
c.r.assert.is_a? OpenCascade
|
20
|
+
c.a.b.assert.is_a? OpenCascade
|
21
|
+
|
22
|
+
But if you set a node, then that will be it's value.
|
23
|
+
|
24
|
+
c.a.b = 4
|
25
|
+
c.a.b.assert == 4
|
26
|
+
|
27
|
+
To query a node without causing the auto-creation of an OpenCasade
|
28
|
+
object, use the ?-mark.
|
29
|
+
|
30
|
+
c.a.z?.assert == nil
|
31
|
+
|
32
|
+
OpenCascade also transforms Hashes within Arrays.
|
33
|
+
|
34
|
+
h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 }
|
35
|
+
c = OpenCascade[h]
|
36
|
+
c.x.first.a.assert == 1
|
37
|
+
c.x.last.a.assert == 2
|
38
|
+
|
39
|
+
Like OpenObject, OpenCascade allows you to insert entries as array
|
40
|
+
pairs.
|
41
|
+
|
42
|
+
c = OpenCascade.new
|
43
|
+
c << [:x,8]
|
44
|
+
c << [:y,9]
|
45
|
+
|
46
|
+
c.x.assert == 8
|
47
|
+
c.y.assert == 9
|
48
|
+
|
49
|
+
Finally, you can call methods ending in a !-mark to access the
|
50
|
+
underlying hash (Note that these differ in behavior from the
|
51
|
+
built-in !-methods).
|
52
|
+
|
53
|
+
bk = c.map!{ |k,v| k.to_s.upcase }
|
54
|
+
bk.sort.assert == ['X', 'Y']
|
55
|
+
|
56
|
+
So you can see that for the most an OpenCascade is just like
|
57
|
+
OpenObject, but it allows you to conveniently build open sub-layers.
|
58
|
+
|
@@ -0,0 +1,141 @@
|
|
1
|
+
= FuzzyHash
|
2
|
+
|
3
|
+
Require the library.
|
4
|
+
|
5
|
+
require 'hashery/fuzzyhash'
|
6
|
+
|
7
|
+
Should accept strings and retrieve based on them.
|
8
|
+
|
9
|
+
l = FuzzyHash.new
|
10
|
+
l['asd'] = 'qwe'
|
11
|
+
l['asd'].should == 'qwe'
|
12
|
+
|
13
|
+
Should accept strings, but the second time you set the same string, it should overwrite.
|
14
|
+
|
15
|
+
l = FuzzyHash.new
|
16
|
+
l['asd'] = 'asd'
|
17
|
+
l['asd'] = 'qwe'
|
18
|
+
l['asd'].should == 'qwe'
|
19
|
+
|
20
|
+
Should accept regexs too.
|
21
|
+
|
22
|
+
l = FuzzyHash.new
|
23
|
+
l[/asd.*/] = 'qwe'
|
24
|
+
l['asdqweasd'].should == 'qwe'
|
25
|
+
|
26
|
+
Should accept regexs too, but the second time you set the same regex, it should overwrite.
|
27
|
+
|
28
|
+
l = FuzzyHash.new
|
29
|
+
l[/asd/] = 'asd'
|
30
|
+
l[/asd/] = 'qwe'
|
31
|
+
l['asdqweasd'].should == 'qwe'
|
32
|
+
|
33
|
+
Should accept regexs too with the match.
|
34
|
+
|
35
|
+
l = FuzzyHash.new
|
36
|
+
l[/asd.*/] = 'qwe'
|
37
|
+
l.match_with_result('asdqweasd').should == ['qwe', 'asdqweasd']
|
38
|
+
|
39
|
+
Should accept regexs that match the whole strong too with the match.
|
40
|
+
|
41
|
+
l = FuzzyHash.new
|
42
|
+
l[/asd/] = 'qwe'
|
43
|
+
l.match_with_result('asd').should == ['qwe', 'asd']
|
44
|
+
|
45
|
+
Should prefer string to regex matches.
|
46
|
+
|
47
|
+
l = FuzzyHash.new
|
48
|
+
l['asd'] = 'qwe2'
|
49
|
+
l[/asd.*/] = 'qwe'
|
50
|
+
l['asd'].should == 'qwe2'
|
51
|
+
|
52
|
+
Should allow nil keys.
|
53
|
+
|
54
|
+
l = FuzzyHash.new
|
55
|
+
l[nil] = 'qwe2'
|
56
|
+
l['asd'] = 'qwe'
|
57
|
+
l['asd'].should == 'qwe'
|
58
|
+
l[nil].should == 'qwe2'
|
59
|
+
|
60
|
+
Should allow boolean keys.
|
61
|
+
|
62
|
+
l = FuzzyHash.new
|
63
|
+
l[false] = 'false'
|
64
|
+
l[true] = 'true'
|
65
|
+
l[/.*/] = 'everything else'
|
66
|
+
l[true].should == 'true'
|
67
|
+
l[false].should == 'false'
|
68
|
+
l['false'].should == 'everything else'
|
69
|
+
|
70
|
+
Should pick between the correct regex.
|
71
|
+
|
72
|
+
hash = FuzzyHash.new
|
73
|
+
hash[/^\d+$/] = 'number'
|
74
|
+
hash[/.*/] = 'something'
|
75
|
+
hash['123asd'].should == 'something'
|
76
|
+
|
77
|
+
Should be able to delete by value for hash.
|
78
|
+
|
79
|
+
l = FuzzyHash.new
|
80
|
+
l[nil] = 'qwe2'
|
81
|
+
l['asd'] = 'qwe'
|
82
|
+
l['asd'].should == 'qwe'
|
83
|
+
l[nil].should == 'qwe2'
|
84
|
+
l.delete_value('qwe2')
|
85
|
+
l[nil].should == nil
|
86
|
+
|
87
|
+
Should be able to delete by value for regex.
|
88
|
+
|
89
|
+
l = FuzzyHash.new
|
90
|
+
l[/qwe.*/] = 'qwe2'
|
91
|
+
l['asd'] = 'qwe'
|
92
|
+
l['asd'].should == 'qwe'
|
93
|
+
l['qweasd'].should == 'qwe2'
|
94
|
+
l.delete_value('qwe2')
|
95
|
+
l['qweasd'].should == nil
|
96
|
+
|
97
|
+
Should iterate through the keys.
|
98
|
+
|
99
|
+
l = FuzzyHash.new
|
100
|
+
l[/qwe.*/] = 'qwe2'
|
101
|
+
l['asd'] = 'qwe'
|
102
|
+
l['zxc'] = 'qwe'
|
103
|
+
l.keys.size.should == 3
|
104
|
+
|
105
|
+
Should iterate through the values.
|
106
|
+
|
107
|
+
l = FuzzyHash.new
|
108
|
+
l[/qwe.*/] = 'qwe2'
|
109
|
+
l['asd'] = 'qwe'
|
110
|
+
l['zxc'] = 'qwelkj'
|
111
|
+
(['qwe2','qwe','qwelkj'] & l.values).size.should == 3
|
112
|
+
|
113
|
+
Should clear.
|
114
|
+
|
115
|
+
l = FuzzyHash.new
|
116
|
+
l[/qwe.*/] = 'qwe2'
|
117
|
+
l['asd'] = 'qwe'
|
118
|
+
l['zxc'] = 'qwelkj'
|
119
|
+
l.clear
|
120
|
+
l.empty?.should == true
|
121
|
+
|
122
|
+
Should handle equality.
|
123
|
+
|
124
|
+
l_1 = FuzzyHash.new
|
125
|
+
l_1[/qwe.*/] = 'qwe2'
|
126
|
+
l_1['asd'] = 'qwelkj'
|
127
|
+
l_1['zxc'] = 'qwe'
|
128
|
+
l_2 = FuzzyHash.new
|
129
|
+
l_2['zxc'] = 'qwe'
|
130
|
+
l_2['asd'] = 'qwelkj'
|
131
|
+
l_2[/qwe.*/] = 'qwe2'
|
132
|
+
l_1.should == l_2
|
133
|
+
|
134
|
+
Should return the value when adding the value.
|
135
|
+
|
136
|
+
h = FuzzyHash.new
|
137
|
+
(h[/asd/] = '123').should == '123'
|
138
|
+
(h['qwe'] = '123').should == '123'
|
139
|
+
|
140
|
+
That's It.
|
141
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
= PropertyHash
|
2
|
+
|
3
|
+
Require the library.
|
4
|
+
|
5
|
+
require 'hashery/propertyhash'
|
6
|
+
|
7
|
+
The Property hash can be used an object in itself.
|
8
|
+
|
9
|
+
h = PropertyHash.new(:a=>1, :b=>2)
|
10
|
+
h[:a] #=> 1
|
11
|
+
h[:a] = 3
|
12
|
+
h[:a] #=> 3
|
13
|
+
|
14
|
+
Becuase the properties are fixed, if we try to set a key that is not present,
|
15
|
+
then we will get an error.
|
16
|
+
|
17
|
+
expect ArgumentError do
|
18
|
+
h[:x] = 5
|
19
|
+
end
|
20
|
+
|
21
|
+
The PropertyHash can also be used 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
|
+
expect ArgumentError do
|
36
|
+
h[:x] = 5
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
= OpenStructable
|
2
|
+
|
3
|
+
OpensStructable is a mixin module which can provide OpenStruct behavior to
|
4
|
+
any class or object. OpenStructable allows extention of data objects
|
5
|
+
with arbitrary attributes.
|
6
|
+
|
7
|
+
require 'hashery/ostructable'
|
8
|
+
|
9
|
+
class Record
|
10
|
+
include OpenStructable
|
11
|
+
end
|
12
|
+
|
13
|
+
Now let's create a new record and see if we can assign values to open
|
14
|
+
properties.
|
15
|
+
|
16
|
+
record = Record.new
|
17
|
+
record.name = "John Smith"
|
18
|
+
record.age = 70
|
19
|
+
record.pension = 300
|
20
|
+
|
21
|
+
We can see that the values were set.
|
22
|
+
|
23
|
+
record.name.assert == "John Smith"
|
24
|
+
record.age.assert == 70
|
25
|
+
record.pension.assert == 300
|
26
|
+
|
27
|
+
If we havent' assigned a value to a property it should just return +nil+.
|
28
|
+
|
29
|
+
record.address.assert == nil
|
30
|
+
|
31
|
+
OpenStructable is also smart enough to adjust itself to work with a subclass
|
32
|
+
of a Hash.
|
33
|
+
|
34
|
+
class HashRecord < Hash
|
35
|
+
include OpenStructable
|
36
|
+
end
|
37
|
+
|
38
|
+
We can apply similar settings as above.
|
39
|
+
|
40
|
+
record = HashRecord.new
|
41
|
+
record.name = "John Doe"
|
42
|
+
record.age = 40
|
43
|
+
record.pension = 200
|
44
|
+
|
45
|
+
We can see that the values were set.
|
46
|
+
|
47
|
+
record.name.assert == "John Doe"
|
48
|
+
record.age.assert == 40
|
49
|
+
record.pension.assert == 200
|
50
|
+
|
51
|
+
The differnce here is that the data is accessible via the normal Hash
|
52
|
+
methods too.
|
53
|
+
|
54
|
+
record.assert == {:name=>"John Doe", :age=>40, :pension=>200}
|
55
|
+
|
56
|
+
Notice that entries are converted to Symbols, not Strings.
|