safestruct 0.0.1 → 0.1.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.
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