etheruby 0.0.3 → 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: 0c566325afa370e6f3d4dc52964b205b26a53da7
4
- data.tar.gz: 10cd9eac53229fe3a293fe0cefc879abf9d2cc3f
3
+ metadata.gz: 62e7a1ef2415572fb35e1617898576247fabeccb
4
+ data.tar.gz: 9f6747ff369f23fe11f3a67c0b815b6a53bbfd72
5
5
  SHA512:
6
- metadata.gz: 6dc714bdcc04e5778b508d809e5e29fb230c4a1640f3e377c55d123a12d9929eb879f6d73175274816b7491a0943c2e96a0b60216ce240b2630e3038ff5553e8
7
- data.tar.gz: c3cae2decb6fb2c2f8e79c3d27ed2dcbf3cf2c2ece666382445892cba8f226a4946e1f598a4f2803dd358b0158f35fab32c9cf24b3c1270a83c2ab8a97ea708b
6
+ metadata.gz: b5deae709916778cfd2e39217f22edb24929da0a57054cd5020a0882e8bf222d3b1928d90140a21ce99f9aaeb0eb103f4172031f1d9b857f05aee62ea39e9648
7
+ data.tar.gz: f725746f91570b71d37d89b62c241945688603fedaf17f0fb56db178511eb73206edd72d9d45fba75b88ebdb16458765e242166222d81765b7c03649e99b9051
@@ -1,11 +1,11 @@
1
1
  require 'bigdecimal'
2
2
  require_relative 'type_matchers'
3
+ require_relative 'treat_variable'
4
+ require_relative 'encoders/int'
3
5
 
4
6
  module Etheruby
5
7
 
6
- class IncorrectTypeError < StandardError; end
7
8
  class ArgumentsCountError < StandardError; end
8
- class InvalidFormatForDataError < StandardError; end
9
9
 
10
10
  class ArgumentsGenerator
11
11
 
@@ -16,127 +16,23 @@ module Etheruby
16
16
  @args = args
17
17
  end
18
18
 
19
- def treat_variable(param, arg)
20
- if match = TypeMatchers.is_sized_type(param)
21
- # Parameter is a sized type, e.g. uint256, byte32 ...
22
- send("#{match[1]}_encode".to_sym, match[2].to_i, arg)
23
-
24
- elsif match = TypeMatchers.is_dualsized_type(param)
25
- # Parameter is a dual sized array type, e.g. fixed16x16
26
- send("#{match[1]}_encode".to_sym, match[2].to_i, match[3].to_i, arg)
27
-
28
- elsif match = TypeMatchers.is_static_array_type(param)
29
- # Parameter is a staticly sized array type, e.g. uint256[24]
30
- static_array_encode(match[1], match[2].to_i, arg)
31
-
32
- elsif match = TypeMatchers.is_dynamic_array_type(param)
33
- # Parameter is a dynamicaly sized array type, e.g. uint256[]
34
- dynamic_array_encode(match[1], arg)
35
-
36
- else
37
- # Parameter is a single-word type : string, bytes, address etc...
38
- send("#{param}_encode".to_sym, arg)
39
-
40
- end
41
- end
42
-
43
19
  def to_s
44
- raise ArgumentsCountError.new unless params.count == args.count
45
- (0..params.count-1).map { |i| treat_variable(params[i], args[i]) }.join
46
- end
47
-
48
- ##
49
- # int<X> encoding
50
- def int_encode(size, arg)
51
- if arg >= 0
52
- arg.to_s(16).rjust(size / 4, '0')
53
- else
54
- mask = (1 << size) - 1
55
- (arg & mask).to_s(16)
56
- end
57
- end
58
-
59
- ##
60
- # uint<X> encoding
61
- def uint_encode(size, arg)
62
- raise InvalidFormatForDataError.new("unsigned integer #{arg} < 0") if arg < 0
63
- int_encode(size, arg)
64
- end
65
-
66
- ##
67
- # ufixed<X> encoding
68
- def ufixed_encode(size_i, size_d, arg)
69
- raise InvalidFormatForDataError.new("unsigned fixed #{arg} < 0") if arg < 0
70
- fixed_encode(size_i, size_d, arg)
71
- end
72
-
73
- ##
74
- # fixed<X> encoding
75
- def fixed_encode(size_i, size_d, arg)
76
- raise InvalidFormatForDataError.new("Please use BigDecimal !") unless arg.is_a? BigDecimal
77
- if arg >= 0
78
- int_part, dec_part = arg.to_i, arg - arg.to_i
79
- else
80
- int_part, dec_part = arg.to_i, (arg + arg.to_i.abs).abs
20
+ raise ArgumentsCountError.new("Bad number of arguments") unless args.count == params.count
21
+ head = ''
22
+ tail = ''
23
+ current_tail_position = (params.count*32)
24
+ (0..params.count-1).each do |i|
25
+ param, arg = params[i], args[i]
26
+ if Etheruby.is_static_type? param
27
+ head += Etheruby.treat_variable(param, arg, :encode).to_s
28
+ else
29
+ head += Etheruby::Encoders::Uint.new(current_tail_position).encode
30
+ content = Etheruby.treat_variable(param, arg, :encode).to_s
31
+ current_tail_position += content.length/2
32
+ tail += content
33
+ end
81
34
  end
82
- int_encode(size_i, int_part) + \
83
- decimal_representation(size_d, dec_part)
84
- end
85
-
86
- ##
87
- # Represent the decimal part in hexadimal according to the precision
88
- def decimal_representation(precision, value)
89
- (0..precision-1).map {
90
- int_part, value = (value*2).to_i, (value*2) - (value*2).to_i
91
- int_part
92
- }.each_slice(8).map { |slice|
93
- slice.join.to_i(2).to_s(16).rjust(2,'0')
94
- }.join
95
- end
96
-
97
- ##
98
- # Encode a static array
99
- def static_array_encode(type, size, arg)
100
- raise InvalidFormatForDataError.new(
101
- "Array have #{arg.count} items for #{size} sized variable"
102
- ) unless arg.count == size
103
- arg.map { |item| treat_variable(type, item) }.join
104
- end
105
-
106
- ##
107
- # Creates a dynamic array
108
- def dynamic_array_encode(type, arg)
109
- uint_encode(256, arg.count) + static_array_encode(type, arg.count, arg)
110
- end
111
-
112
- ##
113
- # byte<X> encodeing
114
- def byte_encode(size, arg)
115
- arg.map{ |b| b.to_s(16).rjust(2,'0') }.join.rjust(size,'0')
116
- end
117
-
118
- ##
119
- # address<X> encoding
120
- def address_encode(arg)
121
- uint_encode(160, arg)
122
- end
123
-
124
- ##
125
- # string<x> encoding (as bytes)
126
- def string_encode(arg)
127
- bytes_encode(arg.codepoints)
128
- end
129
-
130
- ##
131
- # bytes (dynamic size) encoding
132
- def bytes_encode(arg)
133
- uint_encode(256, arg.count) + arg.map{ |b| b.to_s(16).rjust(2,'0') }.join
134
- end
135
-
136
- ##
137
- # boolean encoding (as uint8)
138
- def bool_encode(arg)
139
- uint_encode(8, arg ? 1 : 0)
35
+ return head + tail
140
36
  end
141
37
 
142
38
  end
@@ -0,0 +1,17 @@
1
+ require_relative 'base'
2
+ require_relative 'int'
3
+
4
+ module Etheruby::Encoders
5
+
6
+ class Address < Base
7
+ def encode
8
+ Uint.new(data).encode
9
+ end
10
+
11
+ def decode
12
+ v, s = Uint.new(data).decode
13
+ return "0x#{v.to_s(16)}", s
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'base'
2
+ require_relative 'int'
3
+ require_relative '../treat_variable'
4
+
5
+ module Etheruby::Encoders
6
+
7
+ class StaticArray < Base
8
+
9
+ attr_reader :type, :size
10
+
11
+ def initialize(_type, _size, _data)
12
+ super(_data)
13
+ @type = _type
14
+ @size = _size
15
+ end
16
+
17
+ def to_s
18
+ encode
19
+ end
20
+
21
+ def encode
22
+ if Etheruby::is_static_type? type
23
+ data.map{ |d| Etheruby::treat_variable(type, d, :encode) }.join
24
+ else
25
+ head_x = ''
26
+ enc_x = ''
27
+ c_pos = size * 32
28
+ data.map { |d|
29
+ treated = Etheruby::treat_variable(type, d, :encode)
30
+ {size: treated.length/2, value: treated}
31
+ }.each { |item|
32
+ head_x += Uint.new(c_pos).encode
33
+ c_pos += item[:size]
34
+ enc_x += item[:value]
35
+ }
36
+ head_x + enc_x
37
+ end
38
+ end
39
+
40
+ def decode
41
+ if Etheruby::is_static_type?(type)
42
+ sub_decode(0, data)
43
+ else
44
+ sub_decode(size*32, data[size*2*32..data.length])
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def sub_decode(total_taken, real_data)
51
+ values = []
52
+ loop do
53
+ v, s = Etheruby::treat_variable(type, real_data, :decode)
54
+ v, s = v.decode if v.is_a? StaticArray or v.is_a? DynamicArray
55
+ values << v
56
+ total_taken += s
57
+ break if values.count == size
58
+ real_data = real_data[s*2..real_data.length]
59
+ end
60
+ return values, total_taken
61
+ end
62
+
63
+ end
64
+
65
+ class DynamicArray < Base
66
+
67
+ attr_reader :type
68
+
69
+ def initialize(_type, _data)
70
+ super(_data)
71
+ @type = _type
72
+ end
73
+
74
+ def to_s
75
+ encode
76
+ end
77
+
78
+ def encode
79
+ Uint.new(data.count).encode + \
80
+ StaticArray.new(type, data.count, data).encode
81
+ end
82
+
83
+ def decode
84
+ size, taken = Uint.new(data[0..63]).decode
85
+ v, s = StaticArray.new(type, size, data[64..data.length]).decode
86
+ return v, s+taken
87
+ end
88
+
89
+ end
90
+
91
+ end
@@ -0,0 +1,28 @@
1
+ module Etheruby
2
+
3
+ module Encoders
4
+
5
+ class IncorrectTypeError < StandardError; end
6
+ class InvalidFormatForDataError < StandardError; end
7
+
8
+ class Base
9
+ attr_reader :data
10
+
11
+ def initialize(_data)
12
+ @data = _data
13
+ end
14
+
15
+ protected
16
+
17
+ def determinate_closest_padding(size)
18
+ if size % 32 == 0
19
+ size
20
+ else
21
+ 32*(size/32+1)
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'base'
2
+ require_relative 'int'
3
+
4
+ module Etheruby::Encoders
5
+
6
+ class Bool < Base
7
+ def encode
8
+ Uint.new(data ? 1 : 0).encode
9
+ end
10
+
11
+ def decode
12
+ Uint.new(data).decode[0] == 1
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'base'
2
+ require_relative 'int'
3
+
4
+ module Etheruby::Encoders
5
+
6
+ class Byte < Base
7
+
8
+ attr_reader :size
9
+
10
+ def initialize(_size,_data)
11
+ super(_data)
12
+ @size = _size
13
+ end
14
+
15
+ def encode
16
+ data.map{ |b|
17
+ b.to_s(16).rjust(2,'0')
18
+ }.join.ljust(determinate_closest_padding(size)*2, '0')
19
+ end
20
+
21
+ def decode
22
+ return data[0..(size*2)-1].split('').each_slice(2).map{ |b| b.join.to_i(16) },
23
+ determinate_closest_padding(size)
24
+ end
25
+
26
+ end
27
+
28
+ class Bytes < Base
29
+
30
+ def encode
31
+ Uint.new(data.length).encode + Byte.new(data.length, data).encode
32
+ end
33
+
34
+ def decode
35
+ size = Uint.new(data[0..63]).decode[0]
36
+ return Byte.new(size,data[64..data.length]).decode[0], determinate_closest_padding(size+32)
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'base'
2
+ require_relative 'int'
3
+
4
+ module Etheruby::Encoders
5
+
6
+ class FixedBase < Base
7
+ attr_reader :size_i, :size_d
8
+
9
+ def initialize(_size_i, _size_d, _data)
10
+ super(_data)
11
+ @size_i = _size_i
12
+ @size_d = _size_d
13
+ end
14
+
15
+ protected
16
+
17
+ def encode_decimal_representation(value)
18
+ (0..@size_d-1).map {
19
+ int_part, value = (value*2).to_i, (value*2) - (value*2).to_i
20
+ int_part
21
+ }.each_slice(8).map { |slice|
22
+ slice.join.to_i(2).to_s(16).rjust(2,'0')
23
+ }.join
24
+ end
25
+
26
+ def decode_decimal_representation(part)
27
+ value = 0.0
28
+ exp = -1
29
+ bin_rpr = part.to_i(16).to_s(2).rjust(@size_d,'0')
30
+ bin_rpr.split("").each do |bit|
31
+ value += 2**exp if bit == '1'
32
+ exp -= 1
33
+ end
34
+ value
35
+ end
36
+ end
37
+
38
+ class Fixed < FixedBase
39
+
40
+ def encode
41
+ if data >= 0
42
+ int_part, dec_part = data.to_i, data - data.to_i
43
+ else
44
+ int_part, dec_part = data.to_i, (data + data.to_i.abs).abs
45
+ end
46
+ Int.new(int_part).encode(size_i/4) + \
47
+ encode_decimal_representation(dec_part).ljust((256-size_i)/4,'0')
48
+ end
49
+
50
+ def decode
51
+ @data = data[0..63]
52
+ int_part = Int.new(data[0..(size_i/4)-1]).decode[0]
53
+ dec_part = decode_decimal_representation(data[(size_i / 4)..data.length])
54
+ if int_part >= 0
55
+ return (dec_part + int_part), 32
56
+ else
57
+ return -(dec_part + int_part.abs), 32
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ class Ufixed < FixedBase
64
+
65
+ def encode
66
+ raise InvalidFormatForDataError.new("Unsigned fixed #{data} < 0") if data < 0
67
+ Fixed.new(size_i, size_d, data).encode
68
+ end
69
+
70
+ def decode
71
+ @data = data[0..63]
72
+ return Uint.new(data[0..(size_i/4)-1]).decode[0] +
73
+ decode_decimal_representation(data[(size_i / 4)..data.length]), 32
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'base'
2
+
3
+ module Etheruby::Encoders
4
+
5
+ class Int < Base
6
+
7
+ def encode(pad_to=64)
8
+ if data >= 0
9
+ data.to_s(16).rjust(pad_to, '0')
10
+ else
11
+ mask = (1 << pad_to*4) - 1
12
+ (data & mask).to_s(16).rjust(pad_to, 'f')
13
+ end
14
+ end
15
+
16
+ def decode
17
+ in_int = data[0..63].to_i(16)
18
+ in_bin = in_int.to_s(2).rjust(64, '0')
19
+ if in_bin[0] == '1'
20
+ return -(in_bin.split("").map{ |i| i == '1' ? '0' : '1' }.join.to_i(2) + 1), 32
21
+ else
22
+ return in_int, 32
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ class Uint < Base
29
+
30
+ def encode(pad_to=64)
31
+ raise InvalidFormatForDataError.new("Unsigned integer #{data} < 0") if data < 0
32
+ Int.new(data).encode(pad_to)
33
+ end
34
+
35
+ def decode
36
+ return data[0..63].to_i(16), 32
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'base'
2
+ require_relative 'bytes'
3
+
4
+ module Etheruby::Encoders
5
+
6
+ class String < Base
7
+ def encode
8
+ Bytes.new(data.codepoints).encode
9
+ end
10
+
11
+ def decode
12
+ v, s = Bytes.new(data).decode
13
+ return v.pack('U*'), s
14
+ end
15
+ end
16
+
17
+ end
@@ -4,107 +4,23 @@ module Etheruby
4
4
 
5
5
  class ResponseParser
6
6
 
7
+ attr_reader :returns, :response
8
+
7
9
  def initialize(_returns, _response)
8
10
  @returns = _returns
9
- @response = _response
10
- end
11
-
12
- def treat_variable(param)
13
- if match = TypeMatchers.is_sized_type(param)
14
- # Parameter is a sized type, e.g. uint256, byte32 ...
15
- send("#{match[1]}_decode".to_sym, match[2].to_i)
16
-
17
- elsif match = TypeMatchers.is_dualsized_type(param)
18
- # Parameter is a dual sized array type, e.g. fixed16x16
19
- send("#{match[1]}_decode".to_sym, match[2].to_i, match[3].to_i)
20
-
21
- elsif match = TypeMatchers.is_static_array_type(param)
22
- # Parameter is a staticly sized array type, e.g. uint256[24]
23
- static_array_decode(match[1], match[2].to_i)
24
-
25
- elsif match = TypeMatchers.is_dynamic_array_type(param)
26
- # Parameter is a dynamicaly sized array type, e.g. uint256[]
27
- dynamic_array_decode(match[1])
28
-
29
- else
30
- # Parameter is a single-word type : string, bytes, address etc...
31
- send("#{param}_decode".to_sym)
32
-
33
- end
11
+ @response = _response[2.._response.length]
34
12
  end
35
13
 
36
14
  def parse
37
- if @returns.count == 1
38
- treat_variable(@returns[0])
15
+ if returns.count == 1
16
+ Etheruby::treat_variable(returns[0], response, :decode)
39
17
  else
40
- @returns.map { |type| treat_variable(type) }
18
+ returns.each do |type|
19
+
20
+ end
41
21
  end
42
22
  end
43
23
 
44
- # Each decode method will receive in parameters the response string remaining
45
- # to parse. It will extract the type of the response considering it as the
46
- # first thing in the string and returns the string without it. Doing this,
47
- # method calls will be chainable easily.
48
-
49
- ##
50
- # int<X> decoding
51
- def int_decode(size)
52
- end
53
-
54
- ##
55
- # uint<X> decoding
56
- def uint_decode(size)
57
- v, @response = @response[0..(size/4)].to_i(16),
58
- @response[(size/4)..@response.length]
59
- v
60
- end
61
-
62
- ##
63
- # ufixed<X> decoding
64
- def ufixed_decode(size_i, size_d)
65
- end
66
-
67
- ##
68
- # fixed<X> decoding
69
- def fixed_decode(size_i, size_d)
70
- end
71
-
72
- ##
73
- # Decodes a static array
74
- def static_array_decode(type, size)
75
- end
76
-
77
- ##
78
- # Decodes a dynamic array
79
- def dynamic_array_decode(type)
80
- end
81
-
82
- ##
83
- # byte<X> decoding
84
- def byte_decode(size)
85
- end
86
-
87
- ##
88
- # address<X> decoding
89
- def address_decode
90
- end
91
-
92
- ##
93
- # string<x> decoding (as bytes)
94
- def string_decode
95
- end
96
-
97
- ##
98
- # bytes (dynamic size) decoding
99
- def bytes_decode
100
- end
101
-
102
- ##
103
- # boolean decoding (as uint8)
104
- def bool_decode
105
- uint_decode(8) == 1
106
- end
107
-
108
24
  end
109
25
 
110
26
  end
@@ -0,0 +1,52 @@
1
+ require_relative 'encoders/address'
2
+ require_relative 'encoders/arrays'
3
+ require_relative 'encoders/bool'
4
+ require_relative 'encoders/bytes'
5
+ require_relative 'encoders/fixed'
6
+ require_relative 'encoders/int'
7
+ require_relative 'encoders/string'
8
+ require_relative 'type_matchers'
9
+
10
+ module Etheruby
11
+
12
+ def is_static_type?(param)
13
+ !(%w(string bytes).include?(param.to_s) || TypeMatchers.is_dynamic_array_type(param))
14
+ end
15
+
16
+ def is_array?(param)
17
+ TypeMatchers.is_static_array_type(param) || TypeMatchers.is_dynamic_array_type(param)
18
+ end
19
+
20
+ def treat_variable(param, arg, direction)
21
+ if match = TypeMatchers.is_sized_type(param)
22
+ # Parameter is a sized type, e.g. uint256, byte32 ...
23
+ klass = Etheruby::Encoders.const_get(match[1].capitalize)
24
+ return case klass
25
+ when Encoders::Bytes
26
+ Byte.new(match[2].to_i, arg)
27
+ else
28
+ klass.new(arg)
29
+ end.send(direction)
30
+
31
+ elsif match = TypeMatchers.is_dualsized_type(param)
32
+ # Parameter is a dual sized array type, e.g. fixed16x16, ufixed128x128
33
+ return Etheruby::Encoders.const_get(match[1].capitalize).
34
+ new(match[2].to_i, match[3].to_i, arg).send(direction)
35
+
36
+ elsif match = TypeMatchers.is_static_array_type(param)
37
+ # Parameter is a staticly sized array type, e.g. uint256[24]
38
+ Etheruby::Encoders::StaticArray.new(match[1], match[2].to_i, arg)
39
+
40
+ elsif match = TypeMatchers.is_dynamic_array_type(param)
41
+ # Parameter is a dynamicaly sized array type, e.g. uint256[]
42
+ Etheruby::Encoders::DynamicArray.new(match[1], arg).send(direction)
43
+
44
+ else
45
+ # Parameter is a single-word type : string, bytes, address etc...
46
+ Etheruby::Encoders.const_get(param.capitalize).new(arg).send(direction)
47
+
48
+ end
49
+ end
50
+
51
+ module_function :treat_variable, :is_static_type?, :is_array?
52
+ end
@@ -7,7 +7,7 @@ module Etheruby
7
7
  end
8
8
 
9
9
  def is_dualsized_type(param)
10
- param.to_s.match /^(.+)(\d+)x(\d+)$/
10
+ param.to_s.match /^([a-z]+)(\d+)x(\d+)$/
11
11
  end
12
12
 
13
13
  def is_static_array_type(param)
data/lib/etheruby.rb CHANGED
@@ -47,7 +47,7 @@ module Etheruby
47
47
  composed_body[kw] = method_info[kw] if method_info.has_key? kw
48
48
  }
49
49
  @@logger.debug("Calling #{method_info[:name]} with parameters #{composed_body.inspect}")
50
- response = Client.eth.call composed_body, "latest"
50
+ response = call_api composed_body
51
51
  if response.has_key? 'error'
52
52
  @@logger.error("Failed contract execution #{response['error']['message']}")
53
53
  else
@@ -60,6 +60,10 @@ module Etheruby
60
60
  end
61
61
  end
62
62
 
63
+ def self.call_api(composed_body)
64
+ Client.eth.call composed_body, "latest"
65
+ end
66
+
63
67
  def self.address
64
68
  "0x#{@@address.to_s(16)}"
65
69
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etheruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jérémy SEBAN
@@ -91,7 +91,16 @@ files:
91
91
  - lib/etheruby/arguments_generator.rb
92
92
  - lib/etheruby/client.rb
93
93
  - lib/etheruby/contract_method_dsl.rb
94
+ - lib/etheruby/encoders/address.rb
95
+ - lib/etheruby/encoders/arrays.rb
96
+ - lib/etheruby/encoders/base.rb
97
+ - lib/etheruby/encoders/bool.rb
98
+ - lib/etheruby/encoders/bytes.rb
99
+ - lib/etheruby/encoders/fixed.rb
100
+ - lib/etheruby/encoders/int.rb
101
+ - lib/etheruby/encoders/string.rb
94
102
  - lib/etheruby/response_parser.rb
103
+ - lib/etheruby/treat_variable.rb
95
104
  - lib/etheruby/type_matchers.rb
96
105
  homepage: https://github.com/MechanicalSloth/etheruby
97
106
  licenses: