safestruct 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +7 -0
- data/lib/safestruct.rb +59 -2
- data/lib/safestruct/safe_array.rb +69 -0
- data/lib/safestruct/safe_hash.rb +77 -0
- data/lib/safestruct/safe_struct.rb +79 -0
- data/lib/safestruct/version.rb +2 -2
- data/test/helper.rb +8 -0
- data/test/test_array.rb +52 -0
- data/test/test_hash.rb +77 -0
- data/test/test_struct.rb +77 -0
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aabbb2a559ec2421cc0ac43cdcfb0764c8f01633
|
4
|
+
data.tar.gz: 7c4aa56416aa26f67f06aa09260d0af12f28cf27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b51b663bc4580a435e39d74ea015bc6e407dec307126de09fe818977de527ced949d4d58a2a04f27a10a25074e76fcc17f47139152782804177b28bf0cd5d20
|
7
|
+
data.tar.gz: 5285c818c0b4240f186bbc3f9c4221aa4cdf204ec20f0ac187da6b3b1d2a8a339fe5162802b9fabc8b09c80c2ce77e8a013bb3fb391074229dc978fb886024dd
|
data/Manifest.txt
CHANGED
data/lib/safestruct.rb
CHANGED
@@ -6,6 +6,63 @@ require 'pp'
|
|
6
6
|
## our own code
|
7
7
|
require 'safestruct/version' # note: let version always go first
|
8
8
|
|
9
|
+
require 'safestruct/safe_array'
|
10
|
+
require 'safestruct/safe_hash'
|
11
|
+
require 'safestruct/safe_struct'
|
9
12
|
|
10
|
-
|
11
|
-
|
13
|
+
|
14
|
+
|
15
|
+
####################################
|
16
|
+
## add dummy bool class for mapping and (payable) method signature
|
17
|
+
|
18
|
+
class Integer
|
19
|
+
def self.zero() 0; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Bool
|
23
|
+
def self.zero() false; end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
#####
|
29
|
+
# add "beautiful" convenience helpers
|
30
|
+
|
31
|
+
class Mapping
|
32
|
+
|
33
|
+
def self.of( *args )
|
34
|
+
## e.g. gets passed in [{Address=>Integer}]
|
35
|
+
## check for Integer - use Hash.new(0)
|
36
|
+
## check for Bool - use Hash.new(False)
|
37
|
+
if args[0].is_a? Hash
|
38
|
+
arg = args[0].to_a ## convert to array (for easier access)
|
39
|
+
klass_key = arg[0][0]
|
40
|
+
klass_value = arg[0][1]
|
41
|
+
klass = SafeHash.build_class( klass_key, klass_value )
|
42
|
+
klass.new
|
43
|
+
else
|
44
|
+
## todo/fix: throw argument error/exception
|
45
|
+
Hash.new ## that is, "plain" {} with all "standard" defaults
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Array
|
52
|
+
## "typed" safe array "constructor"
|
53
|
+
## e.g. Array.of( Address ) or Array.of( Money ) or Array.of( Proposal, size: 2 ) etc.
|
54
|
+
def self.of( klass_value )
|
55
|
+
klass = SafeArray.build_class( klass_value )
|
56
|
+
klass.new ## todo: add klass.new( **kwargs ) for size: 2 etc.
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
############################
|
62
|
+
# note: HACK redefine built in struct
|
63
|
+
# => warning: already initialized constant Struct
|
64
|
+
OldStruct = Struct ## save old struct class
|
65
|
+
Struct = SafeStruct
|
66
|
+
|
67
|
+
|
68
|
+
puts Safe.banner ## say hello
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class SafeArray
|
4
|
+
|
5
|
+
## e.g.
|
6
|
+
## Array.of( Address ), Array.of( Integer), etc.
|
7
|
+
|
8
|
+
def self.build_class( klass_value )
|
9
|
+
## note: care for now only about value type / class
|
10
|
+
|
11
|
+
## note: keep a class cache
|
12
|
+
cache = @@cache ||= {}
|
13
|
+
klass = cache[ klass_value ]
|
14
|
+
return klass if klass
|
15
|
+
|
16
|
+
klass = Class.new( SafeArray )
|
17
|
+
klass.class_eval( <<RUBY )
|
18
|
+
def self.klass_value
|
19
|
+
@klass_value ||= #{klass_value}
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
## add to cache for later (re)use
|
23
|
+
cache[ klass_value ] = klass
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.new_zero() new; end
|
29
|
+
def self.zero() @zero ||= new_zero; end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
## todo/check: if array works if value is a (nested/multi-dimensional) array
|
35
|
+
@ary = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def []=(index, value)
|
39
|
+
@ary[index] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](index)
|
43
|
+
item = @ary[ index ]
|
44
|
+
if item.nil?
|
45
|
+
## todo/check:
|
46
|
+
## always return (deep) frozen zero object - why? why not?
|
47
|
+
## let user change the returned zero object - why? why not?
|
48
|
+
if self.class.klass_value.respond_to?( :new_zero )
|
49
|
+
## note: use a new unfrozen copy of the zero object
|
50
|
+
## changes to the object MUST be possible (new "empty" modifable object expected)
|
51
|
+
item = self.class.klass_value.new_zero
|
52
|
+
else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
|
53
|
+
## puts "use value semantics"
|
54
|
+
item = self.class.klass_value.zero
|
55
|
+
end
|
56
|
+
end
|
57
|
+
item
|
58
|
+
end
|
59
|
+
|
60
|
+
def push( item )
|
61
|
+
## todo/fix: check if item.is_a? @type
|
62
|
+
## note: Address might be a String too (Address | String)
|
63
|
+
## store Address always as String!!! - why? why not?
|
64
|
+
@ary.push( item )
|
65
|
+
end
|
66
|
+
|
67
|
+
def size() @ary.size; end
|
68
|
+
def length() size; end
|
69
|
+
end # class SafeArray
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
class SafeHash
|
5
|
+
|
6
|
+
## e.g.
|
7
|
+
## Mapping.of( Address => Money )
|
8
|
+
|
9
|
+
## note: need to create new class!! for every mapping
|
10
|
+
## make klass_key class and
|
11
|
+
## klass_value class into class instance variables
|
12
|
+
## that can get used by zero
|
13
|
+
## self.new returns a Hash.new/SafeHash.new like object
|
14
|
+
|
15
|
+
def self.build_class( klass_key, klass_value )
|
16
|
+
## note: care for now only about value type / class
|
17
|
+
|
18
|
+
## note: keep a class cache
|
19
|
+
cache = @@cache ||= {}
|
20
|
+
klass = cache[ klass_value ]
|
21
|
+
return klass if klass
|
22
|
+
|
23
|
+
klass = Class.new( SafeHash )
|
24
|
+
klass.class_eval( <<RUBY )
|
25
|
+
def self.klass_key
|
26
|
+
@klass_key ||= #{klass_key}
|
27
|
+
end
|
28
|
+
def self.klass_value
|
29
|
+
@klass_value ||= #{klass_value}
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
## add to cache for later (re)use
|
33
|
+
cache[ klass_value ] = klass
|
34
|
+
klass
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def self.new_zero() new; end
|
39
|
+
def self.zero() @zero ||= new_zero; end
|
40
|
+
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
## todo/check: if hash works if value is a (nested) hash
|
44
|
+
@h = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def []=(key, value)
|
49
|
+
@h[key] = value
|
50
|
+
end
|
51
|
+
|
52
|
+
def [](key)
|
53
|
+
item = @h[ key ]
|
54
|
+
if item.nil?
|
55
|
+
## pp self.class.klass_value
|
56
|
+
## pp self.class.klass_value.zero
|
57
|
+
|
58
|
+
#####
|
59
|
+
# todo/check:
|
60
|
+
# add zero to hash on lookup (increases size/length)
|
61
|
+
# why? why not?
|
62
|
+
|
63
|
+
if self.class.klass_value.respond_to?( :new_zero )
|
64
|
+
## note: use a dup(licated) unfrozen copy of the zero object
|
65
|
+
## changes to the object MUST be possible (new "empty" modifable object expected)
|
66
|
+
item = @h[ key ] = self.class.klass_value.new_zero
|
67
|
+
else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
|
68
|
+
## puts "use value semantics"
|
69
|
+
item = @h[ key ] = self.class.klass_value.zero
|
70
|
+
end
|
71
|
+
end
|
72
|
+
item
|
73
|
+
end
|
74
|
+
|
75
|
+
def size() @h.size; end
|
76
|
+
def length() size; end
|
77
|
+
end # class SafeHash
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class SafeStruct
|
4
|
+
|
5
|
+
def self.build_class( **attributes )
|
6
|
+
klass = Class.new( SafeStruct ) do
|
7
|
+
define_method( :initialize ) do |*args|
|
8
|
+
attributes.keys.zip( args ).each do |key, arg|
|
9
|
+
instance_variable_set( "@#{key}", arg )
|
10
|
+
end
|
11
|
+
self ## note: return reference to self for chaining method calls
|
12
|
+
end
|
13
|
+
|
14
|
+
attributes.each do |key,value|
|
15
|
+
define_method( key ) do
|
16
|
+
instance_variable_get( "@#{key}" )
|
17
|
+
end
|
18
|
+
if value == false ## note: for Bool add getter with question mark (e.g. voted? etc.)
|
19
|
+
define_method( "#{key}?" ) do
|
20
|
+
instance_variable_get( "@#{key}" )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
define_method( "#{key}=" ) do |arg|
|
24
|
+
instance_variable_set( "@#{key}", arg )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method( :== ) do |other|
|
29
|
+
return false unless other.is_a?( klass )
|
30
|
+
attributes.keys.all? do |key|
|
31
|
+
__send__( key ) == other.__send__( key )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias_method :eql?, :==
|
35
|
+
end
|
36
|
+
|
37
|
+
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
38
|
+
klass.define_singleton_method( :new ) do |*args|
|
39
|
+
if args.size != attributes.size
|
40
|
+
## check for required args/params - all MUST be passed in!!!
|
41
|
+
raise ArgumentError.new( "[SafeStruct] wrong number of arguments for #{name}.new - #{args.size} for #{attributes.size}" )
|
42
|
+
end
|
43
|
+
old_new( *args )
|
44
|
+
end
|
45
|
+
|
46
|
+
klass.define_singleton_method( :new_zero ) do
|
47
|
+
## note: if attribute value is a composite
|
48
|
+
## use new_zero to create a new instance!!!
|
49
|
+
## do NOT use the passed in reference!!!!
|
50
|
+
values = attributes.values.map do |value|
|
51
|
+
if value.is_a?(SafeStruct) ||
|
52
|
+
value.is_a?(SafeArray) ||
|
53
|
+
value.is_a?(SafeHash)
|
54
|
+
value.class.new_zero
|
55
|
+
else
|
56
|
+
## assume "value" object / semantics (e.g. 0, false, '0x0000' etc.) and pass through
|
57
|
+
value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
old_new( *values )
|
61
|
+
end
|
62
|
+
|
63
|
+
klass
|
64
|
+
end # method build_class
|
65
|
+
|
66
|
+
class << self
|
67
|
+
alias_method :old_new, :new # note: store "old" orginal version of new
|
68
|
+
alias_method :new, :build_class # replace original version with create
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.zero
|
72
|
+
## note: freeze return new zero (for "singelton" & "immutable" zero instance)
|
73
|
+
## todo/fix:
|
74
|
+
## in build_class add freeze for composite/reference objects
|
75
|
+
## that is, arrays, hash mappings, structs etc.
|
76
|
+
## freeze only works for now for "value" objects e.g. integer, bool, etc.
|
77
|
+
@zero ||= new_zero.freeze
|
78
|
+
end
|
79
|
+
end # class SafeStruct
|
data/lib/safestruct/version.rb
CHANGED
data/test/helper.rb
ADDED
data/test/test_array.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_array.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestArray < MiniTest::Test
|
12
|
+
|
13
|
+
Array_Integer = SafeArray.build_class( Integer )
|
14
|
+
Array_Bool = SafeArray.build_class( Bool )
|
15
|
+
|
16
|
+
def test_integer
|
17
|
+
pp Array_Integer
|
18
|
+
pp ary = Array_Integer.new
|
19
|
+
|
20
|
+
assert_equal Integer, Array_Integer.klass_value
|
21
|
+
assert_equal 0, ary[0]
|
22
|
+
assert_equal 0, ary[1]
|
23
|
+
|
24
|
+
ary[0] = 101
|
25
|
+
ary[1] = 102
|
26
|
+
assert_equal 101, ary[0]
|
27
|
+
assert_equal 102, ary[1]
|
28
|
+
|
29
|
+
## check Array.of (uses cached classes)
|
30
|
+
assert_equal Array_Integer, Array.of( Integer ).class
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def test_bool
|
35
|
+
pp Array_Bool
|
36
|
+
pp ary = Array_Bool.new
|
37
|
+
|
38
|
+
assert_equal Bool, Array_Bool.klass_value
|
39
|
+
assert_equal false, ary[0]
|
40
|
+
assert_equal false, ary[1]
|
41
|
+
|
42
|
+
ary[0] = true
|
43
|
+
ary[1] = true
|
44
|
+
assert_equal true, ary[0]
|
45
|
+
assert_equal true, ary[1]
|
46
|
+
|
47
|
+
## check Array.of (uses cached classes)
|
48
|
+
assert_equal Array_Bool, Array.of( Bool ).class
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end # class TestArray
|
data/test/test_hash.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_hash.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestHash < MiniTest::Test
|
12
|
+
|
13
|
+
## sig: [Integer, Bool, Integer, Address]
|
14
|
+
Voter = SafeStruct.new( weight: 0, voted: false, vote: 0, delegate: '0x0000' )
|
15
|
+
|
16
|
+
Hash_X_Integer = SafeHash.build_class( String, Integer )
|
17
|
+
Hash_X_Bool = SafeHash.build_class( String, Bool )
|
18
|
+
Hash_X_Voter = SafeHash.build_class( String, Voter )
|
19
|
+
|
20
|
+
def test_integer
|
21
|
+
pp Hash_X_Integer
|
22
|
+
pp h = Hash_X_Integer.new
|
23
|
+
|
24
|
+
assert_equal Integer, Hash_X_Integer.klass_value
|
25
|
+
assert_equal 0, h['0x1111']
|
26
|
+
assert_equal 0, h['0x2222']
|
27
|
+
|
28
|
+
h['0x1111'] = 101
|
29
|
+
h['0x2222'] = 102
|
30
|
+
assert_equal 101, h['0x1111']
|
31
|
+
assert_equal 102, h['0x2222']
|
32
|
+
|
33
|
+
## check Mapping.of (uses cached classes)
|
34
|
+
assert_equal Hash_X_Integer, Mapping.of( String => Integer ).class
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def test_bool
|
39
|
+
pp Hash_X_Bool
|
40
|
+
pp h = Hash_X_Bool.new
|
41
|
+
|
42
|
+
assert_equal Bool, Hash_X_Bool.klass_value
|
43
|
+
assert_equal false, h['0x1111']
|
44
|
+
assert_equal false, h['0x2222']
|
45
|
+
|
46
|
+
h['0x1111'] = true
|
47
|
+
h['0x2222'] = true
|
48
|
+
assert_equal true, h['0x1111']
|
49
|
+
assert_equal true, h['0x2222']
|
50
|
+
|
51
|
+
## check Mapping.of (uses cached classes)
|
52
|
+
assert_equal Hash_X_Bool, Mapping.of( String => Bool ).class
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def test_voter
|
57
|
+
pp Hash_X_Voter
|
58
|
+
pp h = Hash_X_Voter.new
|
59
|
+
|
60
|
+
assert_equal Voter, Hash_X_Voter.klass_value
|
61
|
+
assert_equal Voter.zero, h['0x1111']
|
62
|
+
assert_equal Voter.zero, h['0x2222']
|
63
|
+
|
64
|
+
h['0x1111'].voted = true
|
65
|
+
h['0x2222'].voted = true
|
66
|
+
assert_equal true, h['0x1111'].voted?
|
67
|
+
assert_equal true, h['0x2222'].voted?
|
68
|
+
|
69
|
+
pp h['0x1111']
|
70
|
+
pp h['0x2222']
|
71
|
+
|
72
|
+
## check Mapping.of (uses cached classes)
|
73
|
+
assert_equal Hash_X_Voter, Mapping.of( String => Voter ).class
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end # class TestHash
|
data/test/test_struct.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_struct.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestStruct < MiniTest::Test
|
12
|
+
|
13
|
+
## sig: [Integer, Bool, Integer, Address]
|
14
|
+
Voter = SafeStruct.new( weight: 0, voted: false, vote: 0, delegate: '0x0000' )
|
15
|
+
|
16
|
+
## sig: [Address, Integer, Integer, Money]
|
17
|
+
Bet = SafeStruct.new( user: '0x0000', block: 0, cap: 0, amount: 0 )
|
18
|
+
|
19
|
+
|
20
|
+
def test_voter
|
21
|
+
assert_equal Voter.zero, Voter.zero
|
22
|
+
assert_equal '0x0000', Voter.zero.delegate
|
23
|
+
assert_equal false, Voter.zero.voted?
|
24
|
+
assert_equal 0, Voter.zero.weight
|
25
|
+
assert_equal 0, Voter.zero.vote
|
26
|
+
assert_equal true, Voter.zero.frozen?
|
27
|
+
|
28
|
+
voter = Voter.new_zero
|
29
|
+
|
30
|
+
assert_equal false, voter.frozen?
|
31
|
+
assert_equal Voter.zero, voter
|
32
|
+
assert Voter.zero == voter
|
33
|
+
assert Voter.zero.eql?( voter )
|
34
|
+
|
35
|
+
voter.delegate = '0x1111'
|
36
|
+
pp voter
|
37
|
+
|
38
|
+
assert Voter.zero != voter
|
39
|
+
|
40
|
+
voter2 = Voter.new( 0, false, 0, '0x0000' )
|
41
|
+
assert_equal Voter.zero, voter2
|
42
|
+
assert_equal false, voter2.frozen?
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_bet
|
46
|
+
pp Bet
|
47
|
+
bet = Bet.new_zero
|
48
|
+
pp bet
|
49
|
+
|
50
|
+
assert_equal Bet.zero, bet
|
51
|
+
|
52
|
+
bet.cap = 20_000
|
53
|
+
bet.amount = 100
|
54
|
+
|
55
|
+
assert_equal false, bet.frozen?
|
56
|
+
assert_equal 20_000, bet.cap
|
57
|
+
assert_equal 100, bet.amount
|
58
|
+
assert Bet.zero != bet
|
59
|
+
|
60
|
+
pp bet
|
61
|
+
|
62
|
+
pp Bet.zero
|
63
|
+
pp Bet.zero
|
64
|
+
|
65
|
+
assert_equal Bet.zero, Bet.zero
|
66
|
+
assert_equal '0x0000', Bet.zero.user
|
67
|
+
assert_equal 0, Bet.zero.block
|
68
|
+
assert_equal 0, Bet.zero.cap
|
69
|
+
assert_equal 0, Bet.zero.amount
|
70
|
+
assert_equal true, Bet.zero.frozen?
|
71
|
+
|
72
|
+
bet2 = Bet.new( '0x0000', 0, 0, 0 )
|
73
|
+
assert_equal Bet.zero, bet2
|
74
|
+
assert_equal false, bet2.frozen?
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class TestStruct
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safestruct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
@@ -53,7 +53,14 @@ files:
|
|
53
53
|
- README.md
|
54
54
|
- Rakefile
|
55
55
|
- lib/safestruct.rb
|
56
|
+
- lib/safestruct/safe_array.rb
|
57
|
+
- lib/safestruct/safe_hash.rb
|
58
|
+
- lib/safestruct/safe_struct.rb
|
56
59
|
- lib/safestruct/version.rb
|
60
|
+
- test/helper.rb
|
61
|
+
- test/test_array.rb
|
62
|
+
- test/test_hash.rb
|
63
|
+
- test/test_struct.rb
|
57
64
|
homepage: https://github.com/s6ruby/safestruct
|
58
65
|
licenses:
|
59
66
|
- Public Domain
|