This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
= Hashery
|
||||
|
||||
For the following demos the `hashery` script has
|
||||
been preloaded and the Hashery namespace has been
|
||||
included into the demo context for convenience.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
= OpenHash
|
||||
|
||||
An OpenHash is a Hash that provides +open+ access to its entries via method
|
||||
calls. Writers (methods ending in =-marks) assign entries. Methods without
|
||||
special puncuation will retrieve entries.
|
||||
|
||||
o = OpenHash.new
|
||||
o.a = 1
|
||||
o.b = 2
|
||||
o.a.assert == 1
|
||||
o.b.assert == 2
|
||||
|
||||
Writers always use a Symbol for keys in the underlying Hash.
|
||||
|
||||
o.to_h.assert == { :a=>1, :b=>2 }
|
||||
|
||||
All the usual Hash methods are still available in an OpenHash.
|
||||
|
||||
c = o.map{ |k,v| [k,v] }
|
||||
c.assert.include?([:a,1])
|
||||
c.assert.include?([:b,2])
|
||||
|
||||
And they are protected from being overridden by writers.
|
||||
|
||||
o.map = 3
|
||||
o.map.refute == 3
|
||||
|
||||
Even so, the underlying Hash object does contain the entry even
|
||||
when it cannot be accessed via a reader method.
|
||||
|
||||
o.to_h.assert == { :a=>1, :b=>2, :map=>3 }
|
||||
|
||||
We can see if a method is open or not via the `#open?` method.
|
||||
|
||||
o.open?(:a).assert == true
|
||||
o.open?(:map).assert == false
|
||||
|
||||
For some usecases it may be necessary to give up access to one or
|
||||
more Hash methods in favor of access to the hash entries. This can
|
||||
be done using the `#open!` method.
|
||||
|
||||
o.open!(:map, :merge)
|
||||
o.map.assert == 3
|
||||
o.merge = 4
|
||||
o.merge.assert == 4
|
||||
|
||||
Becuase of nature of a writer, a certain set of Hash methods are always
|
||||
protected, in particluar all methods buffered by underscore (e.g. `__id__`).
|
||||
So these cannot be opened.
|
||||
|
||||
expect ArgumentError do
|
||||
o.open!(:__id__)
|
||||
end
|
||||
|
||||
Even though writers alwasy use Symbols as keys, because an OpenHash
|
||||
is a true Hash object, any object can be used as a key internally.
|
||||
|
||||
o = OpenHash.new
|
||||
o[nil] = "Nothing"
|
||||
o.to_h.assert == { nil=>"Nothing" }
|
||||
|
||||
It simply cannot be accessible via a reader method.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
= QueryHash
|
||||
|
||||
A QueryHash is a Hash that provides open access much like an OpenHash, but it limits readers
|
||||
to bang and query methods (i.e. method ending in `!` or `?`).
|
||||
|
||||
q = QueryHash.new
|
||||
q.a = 1
|
||||
q.b = 2
|
||||
q.a?.assert == 1
|
||||
q.b?.assert == 2
|
||||
|
||||
By default keys are converted to strings.
|
||||
|
||||
q.assert == { "a"=>1, "b"=>2 }
|
||||
|
||||
A QueryHash is compatible with Ruby's standard Hash in every other respect.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
= CastingHash
|
||||
|
||||
A CastingHash is a Hash that allows _casting_ procedures to
|
||||
defined that the keys and values pass through upon assignment.
|
||||
|
||||
c = CastingHash.new
|
||||
c.cast_proc = lambda { |k,v| [k.to_s, v.to_s.upcase] }
|
||||
|
||||
c[:a] = 'a'
|
||||
|
||||
c.assert == {'a'=>'A'}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
= StaticHash
|
||||
|
||||
A StaticHash is simply a Hash that can only be assigned
|
||||
once per key. Once assigned a subsequent attempt to assign
|
||||
a value to the same key will raise an ArgumentError.
|
||||
|
||||
h = StaticHash.new
|
||||
|
||||
h["x"] = 1
|
||||
|
||||
expect ArgumentError do
|
||||
h["x"] = 2
|
||||
end
|
||||
|
||||
The same error will be raised when using #update or #merge!.
|
||||
|
||||
expect ArgumentError do
|
||||
h.update( "x"=>3 )
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
= KeyHash
|
||||
|
||||
The KeyHash is essentially the same as regular Hash but instead
|
||||
of a `default_proc` the initializer takes the `key_proc` for
|
||||
normalizing keys.
|
||||
|
||||
kh = KeyHash.new{ |k| k.to_s.upcase }
|
||||
kh[:a] = 1
|
||||
kh.to_h #=> ({'A'=>1})
|
||||
|
||||
By default, when no `key_proc` is given, it converts all keys to strings.
|
||||
|
||||
kh = KeyHash.new
|
||||
kh[:a] = 1
|
||||
kh.to_h #=> ({'a'=>1})
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
= OpenCascade
|
||||
|
||||
The reason this class is labeled "cascade", is that every internal
|
||||
Hash is transformed into an OpenCascade dynamically upon access.
|
||||
This makes it easy to create _cascading_ references.
|
||||
|
||||
h = { :x => { :y => { :z => 1 } } }
|
||||
c = OpenCascade[h]
|
||||
assert c.x.y.z == 1
|
||||
|
||||
As soon as you access a node it automatically becomes an OpenCascade.
|
||||
|
||||
c = OpenCascade.new
|
||||
assert(OpenCascade === c.r)
|
||||
assert(OpenCascade === c.a.b)
|
||||
|
||||
But if you set a node, then that will be it's value.
|
||||
|
||||
c.a.b = 4
|
||||
assert c.a.b == 4
|
||||
|
||||
To query a node without causing the auto-creation of an OpenCasade
|
||||
object, use the ?-mark.
|
||||
|
||||
assert c.a.z? == nil
|
||||
|
||||
OpenCascade also transforms Hashes within Arrays.
|
||||
|
||||
h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 }
|
||||
c = OpenCascade[h]
|
||||
assert c.x.first.a == 1
|
||||
assert c.x.last.a == 2
|
||||
|
||||
Like OpenObject, OpenCascade allows you to insert entries as array
|
||||
pairs.
|
||||
|
||||
c = OpenCascade.new
|
||||
c << [:x,8]
|
||||
c << [:y,9]
|
||||
|
||||
assert c.x == 8
|
||||
assert c.y == 9
|
||||
|
||||
Finally, you can call methods ending in a !-mark to access the
|
||||
underlying hash (Note that these differ in behavior from the
|
||||
built-in !-methods).
|
||||
|
||||
bk = c.map!{ |k,v| k.to_s.upcase }
|
||||
bk.sort.assert == ['X', 'Y']
|
||||
|
||||
So you can see that for the most an OpenCascade is just like
|
||||
OpenHash, but it allows us to conveniently build open sub-layers
|
||||
easily.
|
||||
|
||||
Enumerable still works with OpenCascades too.
|
||||
|
||||
h = {}
|
||||
c = OpenCascade[:a=>1,:b=>{:c=>3}]
|
||||
c.each do |k,v|
|
||||
h[k] = v
|
||||
end
|
||||
OpenCascade.assert === h[:b]
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
= FuzzyHash
|
||||
|
||||
Should accept strings and retrieve based on them.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l['asd'] = 'qwe'
|
||||
l['asd'].should == 'qwe'
|
||||
|
||||
Should accept strings, but the second time you set the same string, it should overwrite.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l['asd'] = 'asd'
|
||||
l['asd'] = 'qwe'
|
||||
l['asd'].should == 'qwe'
|
||||
|
||||
Should accept regexs too.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/asd.*/] = 'qwe'
|
||||
l['asdqweasd'].should == 'qwe'
|
||||
|
||||
Should accept regexs too, but the second time you set the same regex, it should overwrite.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/asd/] = 'asd'
|
||||
l[/asd/] = 'qwe'
|
||||
l['asdqweasd'].should == 'qwe'
|
||||
|
||||
Should accept regexs too with the match.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/asd.*/] = 'qwe'
|
||||
l.match_with_result('asdqweasd').should == ['qwe', 'asdqweasd']
|
||||
|
||||
Should accept regexs that match the whole strong too with the match.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/asd/] = 'qwe'
|
||||
l.match_with_result('asd').should == ['qwe', 'asd']
|
||||
|
||||
Should prefer string to regex matches.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l['asd'] = 'qwe2'
|
||||
l[/asd.*/] = 'qwe'
|
||||
l['asd'].should == 'qwe2'
|
||||
|
||||
Should allow nil keys.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[nil] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['asd'].should == 'qwe'
|
||||
l[nil].should == 'qwe2'
|
||||
|
||||
Should allow boolean keys.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[false] = 'false'
|
||||
l[true] = 'true'
|
||||
l[/.*/] = 'everything else'
|
||||
l[true].should == 'true'
|
||||
l[false].should == 'false'
|
||||
l['false'].should == 'everything else'
|
||||
|
||||
Should pick between the correct regex.
|
||||
|
||||
hash = FuzzyHash.new
|
||||
hash[/^\d+$/] = 'number'
|
||||
hash[/.*/] = 'something'
|
||||
hash['123asd'].should == 'something'
|
||||
|
||||
Should be able to delete by value for hash.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[nil] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['asd'].should == 'qwe'
|
||||
l[nil].should == 'qwe2'
|
||||
l.delete_value('qwe2')
|
||||
l[nil].should == nil
|
||||
|
||||
Should be able to delete by value for regex.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/qwe.*/] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['asd'].should == 'qwe'
|
||||
l['qweasd'].should == 'qwe2'
|
||||
l.delete_value('qwe2')
|
||||
l['qweasd'].should == nil
|
||||
|
||||
Should iterate through the keys.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/qwe.*/] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['zxc'] = 'qwe'
|
||||
l.keys.size.should == 3
|
||||
|
||||
Should iterate through the values.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/qwe.*/] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['zxc'] = 'qwelkj'
|
||||
(['qwe2','qwe','qwelkj'] & l.values).size.should == 3
|
||||
|
||||
Should clear.
|
||||
|
||||
l = FuzzyHash.new
|
||||
l[/qwe.*/] = 'qwe2'
|
||||
l['asd'] = 'qwe'
|
||||
l['zxc'] = 'qwelkj'
|
||||
l.clear
|
||||
l.empty?.should == true
|
||||
|
||||
Should handle equality.
|
||||
|
||||
l_1 = FuzzyHash.new
|
||||
l_1[/qwe.*/] = 'qwe2'
|
||||
l_1['asd'] = 'qwelkj'
|
||||
l_1['zxc'] = 'qwe'
|
||||
l_2 = FuzzyHash.new
|
||||
l_2['zxc'] = 'qwe'
|
||||
l_2['asd'] = 'qwelkj'
|
||||
l_2[/qwe.*/] = 'qwe2'
|
||||
l_1.should == l_2
|
||||
|
||||
Should return the value when adding the value.
|
||||
|
||||
h = FuzzyHash.new
|
||||
(h[/asd/] = '123').should == '123'
|
||||
(h['qwe'] = '123').should == '123'
|
||||
|
||||
That's It.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
= PropertyHash
|
||||
|
||||
The Property hash can be used an object in itself.
|
||||
|
||||
h = PropertyHash.new(:a=>1, :b=>2)
|
||||
h[:a] #=> 1
|
||||
h[:a] = 3
|
||||
h[:a] #=> 3
|
||||
|
||||
Becuase the properties are fixed, if we try to set a key that is not present,
|
||||
then we will get an error.
|
||||
|
||||
expect ArgumentError do
|
||||
h[:x] = 5
|
||||
end
|
||||
|
||||
The PropertyHash can also be used as a superclass.
|
||||
|
||||
class MyPropertyHash < PropertyHash
|
||||
property :a, :default => 1
|
||||
property :b, :default => 2
|
||||
end
|
||||
|
||||
h = MyPropertyHash.new
|
||||
h[:a] #=> 1
|
||||
h[:a] = 3
|
||||
h[:a] #=> 3
|
||||
|
||||
Again, if we try to set key that was not fixed, then we will get an error.
|
||||
|
||||
expect ArgumentError do
|
||||
h[:x] = 5
|
||||
end
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
= Association
|
||||
|
||||
An Association is a class for creating simple pairings.
|
||||
|
||||
require 'hashery/association'
|
||||
|
||||
An Association can bew created through the usual means
|
||||
of instantiation.
|
||||
|
||||
Association.new(:a, :b)
|
||||
|
||||
Or the shortcut method #>> can be used in most cases.
|
||||
|
||||
:x >> :z
|
||||
|
||||
An association provides two methods to access its content, #index and #value.
|
||||
|
||||
a = 'foo' >> 'bar'
|
||||
|
||||
a.index.assert == 'foo'
|
||||
a.value.assert == 'bar'
|
||||
|
||||
Associations can be used to create ordered-hashes via normal
|
||||
arrays.
|
||||
|
||||
keys = []
|
||||
vals = []
|
||||
|
||||
ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
|
||||
|
||||
ohash.each{ |k,v| keys << k ; vals << v }
|
||||
|
||||
keys.assert == ['A','B','C']
|
||||
vals.assert == ['3','2','1']
|
||||
|
||||
|
||||
Becuase Associations are objects in themselves more complex
|
||||
collections can also be created.
|
||||
|
||||
complex = [
|
||||
'parent' >> 'child',
|
||||
'childless',
|
||||
'another_parent' >> [
|
||||
'subchildless',
|
||||
'subparent' >> 'subchild'
|
||||
]
|
||||
]
|
||||
|
||||
An experimental feature of Association keeps a cache of all defined associations.
|
||||
|
||||
o = Object.new
|
||||
o >> :a
|
||||
o >> :b
|
||||
o >> :c
|
||||
|
||||
o.associations.assert == [:a, :b, :c]
|
||||
|
||||
However this feature will probably be deprecated.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
require 'ae'
|
||||
require 'ae/should'
|
||||
@@ -0,0 +1,3 @@
|
||||
require 'hashery'
|
||||
|
||||
include Hashery
|
||||
Reference in New Issue
Block a user