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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c5bfc55add079e7b118ffd749d00c41b1e65b4b
4
- data.tar.gz: '0830ae1f8e5416333df86fd3600193fff44ea77e'
3
+ metadata.gz: aabbb2a559ec2421cc0ac43cdcfb0764c8f01633
4
+ data.tar.gz: 7c4aa56416aa26f67f06aa09260d0af12f28cf27
5
5
  SHA512:
6
- metadata.gz: d4fc1b8c74807a0ba06ddc4556addac6924c8ecabb2ba63dec1818e21159f870667b0f951213e5804105bd343bc32ac0159da7fb3592013d2b66ed4560f02b59
7
- data.tar.gz: 64cb676719a1bfb17df5cea86dcea78d98a36fb36976548aa32fb95b161eb8b03354c0f66ea1491fc46b153c96a890b60117af55bdd08205271a3569af9504e3
6
+ metadata.gz: 3b51b663bc4580a435e39d74ea015bc6e407dec307126de09fe818977de527ced949d4d58a2a04f27a10a25074e76fcc17f47139152782804177b28bf0cd5d20
7
+ data.tar.gz: 5285c818c0b4240f186bbc3f9c4221aa4cdf204ec20f0ac187da6b3b1d2a8a339fe5162802b9fabc8b09c80c2ce77e8a013bb3fb391074229dc978fb886024dd
data/Manifest.txt CHANGED
@@ -3,4 +3,11 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/safestruct.rb
6
+ lib/safestruct/safe_array.rb
7
+ lib/safestruct/safe_hash.rb
8
+ lib/safestruct/safe_struct.rb
6
9
  lib/safestruct/version.rb
10
+ test/helper.rb
11
+ test/test_array.rb
12
+ test/test_hash.rb
13
+ test/test_struct.rb
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
- pp Safe.banner
11
- pp Safe.root
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
@@ -4,8 +4,8 @@
4
4
  module Safe
5
5
 
6
6
  MAJOR = 0
7
- MINOR = 0
8
- PATCH = 1
7
+ MINOR = 1
8
+ PATCH = 0
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
  def self.version
data/test/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ ## minitest setup
2
+
3
+ require 'minitest/autorun'
4
+
5
+
6
+ ## our own code
7
+
8
+ require 'safestruct'
@@ -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
@@ -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.1
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