charyf_sig 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +39 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +204 -0
- data/Rakefile +48 -0
- data/benchmark/compare.rb +116 -0
- data/lib/charyf_sig.rb +176 -0
- data/lib/sig/kernel.rb +29 -0
- data/lib/sig/none.rb +15 -0
- data/lib/sig/version.rb +4 -0
- data/sig.gemspec +21 -0
- data/spec/sig_spec.rb +342 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed8055a7e00c0933a4d92636952405c230ccd016
|
4
|
+
data.tar.gz: 7f7d37e7b0f432b9c3e18d809101029c27d982dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9575a6279b50b6fd194e4f1f7045bb78732039f5843c05cde34e15501af1598b610903c6431164e06f5d9973903c8e40d1e25470d1aa5bfad1a40f8b27248694
|
7
|
+
data.tar.gz: 3f80d4950303c93991e5a219b6ae9414b42a935602f2d99e51cad925606a5d2af4ec1cabbd32b1225262465fc069ae4132f2338eb6ae66946405fa28abc41b5f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
charyf_sig (1.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ansi (1.5.0)
|
10
|
+
builder (3.2.3)
|
11
|
+
coderay (1.1.2)
|
12
|
+
method_source (0.9.0)
|
13
|
+
minitest (5.10.3)
|
14
|
+
minitest-line (0.6.4)
|
15
|
+
minitest (~> 5.0)
|
16
|
+
minitest-reporters (1.1.18)
|
17
|
+
ansi
|
18
|
+
builder
|
19
|
+
minitest (>= 5.0)
|
20
|
+
ruby-progressbar
|
21
|
+
pry (0.11.1)
|
22
|
+
coderay (~> 1.1.0)
|
23
|
+
method_source (~> 0.9.0)
|
24
|
+
rake (12.1.0)
|
25
|
+
ruby-progressbar (1.9.0)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
charyf_sig!
|
32
|
+
minitest
|
33
|
+
minitest-line
|
34
|
+
minitest-reporters
|
35
|
+
pry
|
36
|
+
rake
|
37
|
+
|
38
|
+
BUNDLED WITH
|
39
|
+
1.15.4
|
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Jan Lelis, mail@janlelis.de
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# `sig`: Optional Type Assertions for Ruby methods. [![[version]](https://badge.fury.io/rb/sig.svg)](http://badge.fury.io/rb/sig) [![[travis]](https://travis-ci.org/janlelis/sig.png)](https://travis-ci.org/janlelis/sig)
|
2
|
+
|
3
|
+
This gem adds the `sig` method that allows you to add signatures to Ruby methods. When you call the method, it will verify that the method's arguments/result fit to the previously defined behavior:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# On main object
|
7
|
+
sig [:to_i, :to_i], Integer,
|
8
|
+
def sum(a, b)
|
9
|
+
a.to_i + b.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
sum(42, false)
|
13
|
+
# Sig::ArgumentTypeError:
|
14
|
+
# - Expected false to respond to :to_i
|
15
|
+
|
16
|
+
# In modules
|
17
|
+
class A
|
18
|
+
sig [Numeric, Numeric], Numeric,
|
19
|
+
def mul(a, b)
|
20
|
+
a * b
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
A.new.mul(4,"3")
|
25
|
+
# Sig::ArgumentTypeError:
|
26
|
+
# - Expected "3" to be a Numeric, but is a String
|
27
|
+
|
28
|
+
|
29
|
+
# Explicitely define signature for singleton_class
|
30
|
+
class B
|
31
|
+
sig_self [:reverse],
|
32
|
+
def self.rev(object)
|
33
|
+
object.reverse
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
B.rev 42
|
38
|
+
# Sig::ArgumentTypeError:
|
39
|
+
# - Expected 42 to respond to :reverse
|
40
|
+
```
|
41
|
+
|
42
|
+
The first argument is an array that defines the behavior of the method arguments, and the second one the behavior of the method result. Don't forget the trailing comma, because the method definition needs to be the last argument to the `sig` method.
|
43
|
+
|
44
|
+
## Features & Design Goals
|
45
|
+
* Provide an intuitive way to define signatures
|
46
|
+
* Only do argument/result type checks, nothing else
|
47
|
+
* Use Ruby's inheritance chain, don't redefine methods
|
48
|
+
* Encourage duck typing
|
49
|
+
* Should work with keyword arguments
|
50
|
+
* Only target Ruby 2.1+
|
51
|
+
|
52
|
+
### This is not static typing. Ruby is a dynamic language:
|
53
|
+
|
54
|
+
Nevertheless, nothing is wrong with ensuring specific behaviour of method arguments when you need it.
|
55
|
+
|
56
|
+
### Is this better than rubype?
|
57
|
+
|
58
|
+
The rubype gem achieves similar things like sig (and inspired the creation of sig). It offers a different syntax and differs in feature & implementation details, so in the end, it is a matter of taste, which gem you prefer.
|
59
|
+
|
60
|
+
## Setup
|
61
|
+
|
62
|
+
Add to your `Gemfile`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
gem 'sig'
|
66
|
+
```
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
See example at top for basic usage.
|
71
|
+
|
72
|
+
### Supported Behavior Types
|
73
|
+
|
74
|
+
You can use the following behavior types in the signature definition:
|
75
|
+
|
76
|
+
Type | Meaning
|
77
|
+
------- | -------
|
78
|
+
Symbol | Argument must respond to a method with this name
|
79
|
+
Module | Argument must be of this module
|
80
|
+
Array | Argument can be of any type found in the array
|
81
|
+
true | Argument must be truthy
|
82
|
+
false | Argument must be falsy
|
83
|
+
nil | Wildcard for any argument
|
84
|
+
|
85
|
+
### Example Signatures
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
sig [:to_i], Numeric, # takes any object that responds to :to_i as argument, numeric result
|
89
|
+
sig [Numeric], String, # one numeric argument, string result
|
90
|
+
sig [Numeric, Numeric], String, # two numeric arguments, string result
|
91
|
+
sig [:to_s, :to_s], # two arguments that support :to_s, don't care about result
|
92
|
+
sig nil, String, # don't care about arguments, as long result is string
|
93
|
+
sig {keyword: Integer} # keyword argument must be an intieger
|
94
|
+
sig [:to_f, {keyword: String}], # mixing positional and keyword arguments is possible
|
95
|
+
sig [[Numeric, NilClass]], Float # one argument that must nil or numeric, result must be float
|
96
|
+
sig [Numeric, nil, Numeric], # first and third argument must be numeric, don't care about type of second
|
97
|
+
```
|
98
|
+
|
99
|
+
See source(https://github.com/janlelis/sig/blob/master/lib/sig.rb) or specs(https://github.com/janlelis/sig/blob/master/spec/sig_spec.rb) for more features.
|
100
|
+
|
101
|
+
## Benchmark (Take with a Grain of Salt)
|
102
|
+
|
103
|
+
You can run `rake benchmark` to run [it](https://github.com/janlelis/sig/blob/v1.0.1/Rakefile#L33-L148) on your machine.
|
104
|
+
|
105
|
+
There is still a lot room for performance improvements. Feel free to suggest some faster implementation to do the type checks (even if it is crazy and not clean, as long it does not add too much "magic", a.k.a does not make debugging harder).
|
106
|
+
|
107
|
+
Note: Starting with 0.3.0, rubype uses a C extension, which makes it much faster. The benchmark is still run with rubype 0.2.5, because 0.3.x currently does not work on jruby & rbx.
|
108
|
+
|
109
|
+
### MRI
|
110
|
+
|
111
|
+
```
|
112
|
+
ruby version: 2.2.2
|
113
|
+
ruby engine: ruby
|
114
|
+
ruby description: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
|
115
|
+
sig version: 1.0.1
|
116
|
+
rubype version: 0.2.5
|
117
|
+
contracts version: 0.9
|
118
|
+
Calculating -------------------------------------
|
119
|
+
pure 107.628k i/100ms
|
120
|
+
sig 14.425k i/100ms
|
121
|
+
rubype 12.715k i/100ms
|
122
|
+
contracts 7.688k i/100ms
|
123
|
+
-------------------------------------------------
|
124
|
+
pure 6.856M (± 0.9%) i/s - 34.333M
|
125
|
+
sig 192.440k (± 1.5%) i/s - 966.475k
|
126
|
+
rubype 164.811k (± 0.8%) i/s - 826.475k
|
127
|
+
contracts 90.089k (± 0.7%) i/s - 453.592k
|
128
|
+
|
129
|
+
Comparison:
|
130
|
+
pure: 6855615.3 i/s
|
131
|
+
sig: 192439.7 i/s - 35.62x slower
|
132
|
+
rubype: 164810.5 i/s - 41.60x slower
|
133
|
+
contracts: 90088.6 i/s - 76.10x slower
|
134
|
+
```
|
135
|
+
|
136
|
+
### JRuby 9000
|
137
|
+
|
138
|
+
```
|
139
|
+
ruby version: 2.2.2
|
140
|
+
ruby engine: jruby
|
141
|
+
ruby description: jruby 9.0.0.0-SNAPSHOT (2.2.2) 2015-05-04 6055b79 Java HotSpot(TM) 64-Bit Server VM 24.80-b11 on 1.7.0_80-b15 +indy +jit [linux-amd64]
|
142
|
+
sig version: 1.0.1
|
143
|
+
rubype version: 0.2.5
|
144
|
+
contracts version: 0.9
|
145
|
+
Calculating -------------------------------------
|
146
|
+
pure 70.898k i/100ms
|
147
|
+
sig 5.308k i/100ms
|
148
|
+
rubype 3.152k i/100ms
|
149
|
+
contracts 279.000 i/100ms
|
150
|
+
-------------------------------------------------
|
151
|
+
pure 8.848M (±13.7%) i/s - 42.539M
|
152
|
+
sig 178.169k (±10.3%) i/s - 881.128k
|
153
|
+
rubype 119.689k (±26.5%) i/s - 444.432k
|
154
|
+
contracts 56.780k (±16.8%) i/s - 265.887k
|
155
|
+
|
156
|
+
Comparison:
|
157
|
+
pure: 8848039.4 i/s
|
158
|
+
sig: 178168.8 i/s - 49.66x slower
|
159
|
+
rubype: 119689.0 i/s - 73.93x slower
|
160
|
+
contracts: 56780.4 i/s - 155.83x slower
|
161
|
+
```
|
162
|
+
|
163
|
+
### RBX 2.5.3
|
164
|
+
|
165
|
+
```
|
166
|
+
ruby version: 2.1.0
|
167
|
+
ruby engine: rbx
|
168
|
+
ruby description: rubinius 2.5.3.c25 (2.1.0 fbb3f1e4 2015-05-02 3.4 JI) [x86_64-linux-gnu]
|
169
|
+
sig version: 1.0.1
|
170
|
+
rubype version: 0.2.5
|
171
|
+
contracts version: 0.9
|
172
|
+
Calculating -------------------------------------
|
173
|
+
pure 114.964k i/100ms
|
174
|
+
sig 9.654k i/100ms
|
175
|
+
rubype 3.775k i/100ms
|
176
|
+
contracts 3.964k i/100ms
|
177
|
+
-------------------------------------------------
|
178
|
+
pure 23.585M (± 3.3%) i/s - 117.263M
|
179
|
+
sig 134.304k (± 3.1%) i/s - 675.780k
|
180
|
+
rubype 56.042k (± 1.7%) i/s - 283.125k
|
181
|
+
contracts 69.820k (± 1.8%) i/s - 348.832k
|
182
|
+
|
183
|
+
Comparison:
|
184
|
+
pure: 23585373.7 i/s
|
185
|
+
sig: 134303.7 i/s - 175.61x slower
|
186
|
+
contracts: 69819.9 i/s - 337.80x slower
|
187
|
+
rubype: 56042.1 i/s - 420.85x slower
|
188
|
+
```
|
189
|
+
|
190
|
+
## Deactivate All Signature Checking
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
require 'sig/none' # instead of require 'sig'
|
194
|
+
```
|
195
|
+
|
196
|
+
## Alternatives for Type Checking and More
|
197
|
+
|
198
|
+
- https://github.com/gogotanaka/Rubype
|
199
|
+
- https://github.com/egonSchiele/contracts.ruby
|
200
|
+
- https://github.com/plum-umd/rtc
|
201
|
+
|
202
|
+
## MIT License
|
203
|
+
|
204
|
+
Copyright (C) 2015 Jan Lelis <http://janlelis.com>. Released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# # #
|
2
|
+
# Get gemspec info
|
3
|
+
|
4
|
+
gemspec_file = Dir['*.gemspec'].first
|
5
|
+
gemspec = eval File.read(gemspec_file), binding, gemspec_file
|
6
|
+
info = "#{gemspec.name} | #{gemspec.version} | " \
|
7
|
+
"#{gemspec.runtime_dependencies.size} dependencies | " \
|
8
|
+
"#{gemspec.files.size} files"
|
9
|
+
|
10
|
+
|
11
|
+
# # #
|
12
|
+
# Gem build and install task
|
13
|
+
|
14
|
+
desc info
|
15
|
+
task :gem do
|
16
|
+
puts info + "\n\n"
|
17
|
+
print " "; sh "gem build #{gemspec_file}"
|
18
|
+
FileUtils.mkdir_p 'pkg'
|
19
|
+
FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
|
20
|
+
puts; sh %{gem install --no-document pkg/#{gemspec.name}-#{gemspec.version}.gem}
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# # #
|
25
|
+
# Start an IRB session with the gem loaded
|
26
|
+
|
27
|
+
desc "#{gemspec.name} | IRB"
|
28
|
+
task :irb do
|
29
|
+
sh "irb -I ./lib -r #{gemspec.name.gsub '-','/'}"
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# # #
|
34
|
+
# Benchmark: Take with a grain of salt
|
35
|
+
|
36
|
+
desc "Compare with contracts and rubype"
|
37
|
+
task :benchmark do
|
38
|
+
ruby "benchmark/compare.rb"
|
39
|
+
end
|
40
|
+
|
41
|
+
# # #
|
42
|
+
# Specs
|
43
|
+
|
44
|
+
desc "Run specs"
|
45
|
+
task :spec do
|
46
|
+
ruby "spec/sig_spec.rb"
|
47
|
+
end
|
48
|
+
task default: :spec
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "benchmark/ips"
|
2
|
+
|
3
|
+
require "rubype"
|
4
|
+
require "rubype/version"
|
5
|
+
require "contracts"
|
6
|
+
require "contracts/version"
|
7
|
+
require_relative "../lib/sig"
|
8
|
+
|
9
|
+
# - - -
|
10
|
+
|
11
|
+
puts "ruby version: #{RUBY_VERSION}"
|
12
|
+
puts "ruby engine: #{RUBY_ENGINE}"
|
13
|
+
puts "ruby description: #{RUBY_DESCRIPTION}"
|
14
|
+
puts "sig version: #{Sig::VERSION}"
|
15
|
+
puts "rubype version: #{Rubype::VERSION}"
|
16
|
+
puts "contracts version: #{Contracts::VERSION}"
|
17
|
+
|
18
|
+
# - - -
|
19
|
+
|
20
|
+
class PureSum
|
21
|
+
def sum(x, y)
|
22
|
+
x + y
|
23
|
+
end
|
24
|
+
|
25
|
+
def mul(x, y)
|
26
|
+
x * y
|
27
|
+
end
|
28
|
+
end
|
29
|
+
pure_instance = PureSum.new
|
30
|
+
|
31
|
+
# - - -
|
32
|
+
|
33
|
+
class SigSum
|
34
|
+
sig [Numeric, Numeric], Numeric,
|
35
|
+
def sum(x, y)
|
36
|
+
x + y
|
37
|
+
end
|
38
|
+
|
39
|
+
sig [:to_i, :to_i], Numeric,
|
40
|
+
def mul(x, y)
|
41
|
+
x * y
|
42
|
+
end
|
43
|
+
end
|
44
|
+
sig_instance = SigSum.new
|
45
|
+
|
46
|
+
# - - -
|
47
|
+
|
48
|
+
class RubypeSum
|
49
|
+
def sum(x, y)
|
50
|
+
x + y
|
51
|
+
end
|
52
|
+
typesig :sum, [Numeric, Numeric] => Numeric
|
53
|
+
|
54
|
+
def mul(x, y)
|
55
|
+
x * y
|
56
|
+
end
|
57
|
+
typesig :mul, [:to_i, :to_i] => Numeric
|
58
|
+
end
|
59
|
+
rubype_instance = RubypeSum.new
|
60
|
+
|
61
|
+
# - - -
|
62
|
+
|
63
|
+
class ContractsSum
|
64
|
+
include Contracts
|
65
|
+
|
66
|
+
Contract Num, Num => Num
|
67
|
+
def sum(x, y)
|
68
|
+
x + y
|
69
|
+
end
|
70
|
+
|
71
|
+
Contract RespondTo[:to_i], RespondTo[:to_i] => Num
|
72
|
+
def mul(x, y)
|
73
|
+
x * y
|
74
|
+
end
|
75
|
+
end
|
76
|
+
contracts_instance = ContractsSum.new
|
77
|
+
|
78
|
+
Benchmark.ips do |x|
|
79
|
+
x.report("pure"){ |times|
|
80
|
+
i = 0
|
81
|
+
while i < times
|
82
|
+
pure_instance.sum(1, 2)
|
83
|
+
pure_instance.mul(1, 2)
|
84
|
+
i += 1
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
x.report("sig"){ |times|
|
89
|
+
i = 0
|
90
|
+
while i < times
|
91
|
+
sig_instance.sum(1, 2)
|
92
|
+
sig_instance.mul(1, 2)
|
93
|
+
i += 1
|
94
|
+
end
|
95
|
+
}
|
96
|
+
|
97
|
+
x.report("rubype"){ |times|
|
98
|
+
i = 0
|
99
|
+
while i < times
|
100
|
+
rubype_instance.sum(1, 2)
|
101
|
+
rubype_instance.mul(1, 2)
|
102
|
+
i += 1
|
103
|
+
end
|
104
|
+
}
|
105
|
+
|
106
|
+
x.report("contracts"){ |times|
|
107
|
+
i = 0
|
108
|
+
while i < times
|
109
|
+
contracts_instance.sum(1, 2)
|
110
|
+
contracts_instance.mul(1, 2)
|
111
|
+
i += 1
|
112
|
+
end
|
113
|
+
}
|
114
|
+
|
115
|
+
x.compare!
|
116
|
+
end
|
data/lib/charyf_sig.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require_relative "sig/version"
|
2
|
+
require_relative "sig/kernel"
|
3
|
+
|
4
|
+
module Sig
|
5
|
+
class ArgumentTypeError < ArgumentError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ResultTypeError < RuntimeError
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.define(object, expected_arguments, expected_result = nil, method_name)
|
12
|
+
no_argument_checks = expected_arguments.nil?
|
13
|
+
|
14
|
+
expected_arguments = Array(expected_arguments)
|
15
|
+
if expected_arguments.last.is_a?(Hash)
|
16
|
+
expected_keyword_arguments = expected_arguments.delete_at(-1)
|
17
|
+
else
|
18
|
+
expected_keyword_arguments = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
method_visibility = get_method_visibility_or_raise(object, method_name)
|
22
|
+
signature_checker = get_or_create_signature_checker(object)
|
23
|
+
signature_checker.send :define_method, method_name do |*arguments, **keyword_arguments|
|
24
|
+
if keyword_arguments.empty?
|
25
|
+
::Sig.check_arguments(expected_arguments, arguments) unless no_argument_checks
|
26
|
+
result = super(*arguments)
|
27
|
+
else
|
28
|
+
::Sig.check_arguments_with_keywords(expected_arguments, arguments,
|
29
|
+
expected_keyword_arguments,
|
30
|
+
keyword_arguments) unless no_argument_checks
|
31
|
+
result = super(*arguments, **keyword_arguments)
|
32
|
+
end
|
33
|
+
::Sig.check_result(expected_result, result) unless expected_result.nil?
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
signature_checker.send(method_visibility, method_name)
|
38
|
+
|
39
|
+
method_name
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.get_method_visibility_or_raise(object, method_name)
|
43
|
+
case
|
44
|
+
when object.private_method_defined?(method_name)
|
45
|
+
:private
|
46
|
+
when object.protected_method_defined?(method_name)
|
47
|
+
:protected
|
48
|
+
when object.public_method_defined?(method_name)
|
49
|
+
:public
|
50
|
+
else
|
51
|
+
raise ArgumentError, "No method with name :#{method_name} for object #{object.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.get_or_create_signature_checker(object)
|
56
|
+
unless checker = object.instance_variable_get(:@_sig)
|
57
|
+
checker = object.instance_variable_set(:@_sig, Module.new)
|
58
|
+
def checker.inspect() "#<Sig:#{object_id}>" end
|
59
|
+
object.prepend(checker)
|
60
|
+
end
|
61
|
+
|
62
|
+
checker
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.check_arguments(expected_arguments, arguments)
|
66
|
+
errors = ""
|
67
|
+
|
68
|
+
arguments.each_with_index{ |argument, index|
|
69
|
+
if error = valid_or_formatted_error(expected_arguments[index], argument)
|
70
|
+
errors << error
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
unless errors.empty?
|
75
|
+
raise ArgumentTypeError, errors
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.check_arguments_with_keywords(expected_arguments, arguments,
|
80
|
+
expected_keyword_arguments, keyword_arguments)
|
81
|
+
errors = ""
|
82
|
+
|
83
|
+
arguments.each_with_index{ |argument, index|
|
84
|
+
if error = valid_or_formatted_error(expected_arguments[index], argument)
|
85
|
+
errors << error
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
if expected_keyword_arguments
|
90
|
+
keyword_arguments.each{ |key, keyword_argument|
|
91
|
+
if error = valid_or_formatted_error(expected_keyword_arguments[key], keyword_argument)
|
92
|
+
errors << error
|
93
|
+
end
|
94
|
+
}
|
95
|
+
elsif error = valid_or_formatted_error(expected_arguments[arguments.size], keyword_arguments)
|
96
|
+
errors << error
|
97
|
+
end
|
98
|
+
|
99
|
+
unless errors.empty?
|
100
|
+
raise ArgumentTypeError, errors
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.check_result(expected_result, result)
|
105
|
+
unless matches? expected_result, result
|
106
|
+
raise ResultTypeError, format_error(expected_result, result)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.matches?(expected, value)
|
111
|
+
# Runtime eval the constants
|
112
|
+
expected = constantize(expected)
|
113
|
+
|
114
|
+
# Match
|
115
|
+
case expected
|
116
|
+
when Array
|
117
|
+
expected.any?{ |expected_element| matches? expected_element, value }
|
118
|
+
when Module
|
119
|
+
value.is_a?(expected)
|
120
|
+
when Symbol
|
121
|
+
value.respond_to?(expected)
|
122
|
+
when Proc
|
123
|
+
!!expected.call(value)
|
124
|
+
when Regexp
|
125
|
+
!!(expected =~ String(value))
|
126
|
+
when Range
|
127
|
+
expected.include?(value)
|
128
|
+
when true
|
129
|
+
!!value
|
130
|
+
when false
|
131
|
+
!value
|
132
|
+
when nil
|
133
|
+
true
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Invalid signature definition: Unknown behavior #{expected}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.constantize(expected)
|
140
|
+
if expected.is_a? String
|
141
|
+
return Object.const_get(expected)
|
142
|
+
elsif expected.is_a? Array
|
143
|
+
return expected.map { |el| constantize(el) }
|
144
|
+
end
|
145
|
+
|
146
|
+
expected
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.valid_or_formatted_error(expected_argument, argument)
|
150
|
+
if !expected_argument.nil? && !matches?(expected_argument, argument)
|
151
|
+
format_error(expected_argument, argument)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.format_error(expected, value)
|
156
|
+
case expected
|
157
|
+
when Array
|
158
|
+
expected.map{ |expected_element| format_error(expected_element, value) }*" OR"
|
159
|
+
when Module, String
|
160
|
+
"\n- Expected #{value.inspect} to be a #{expected}, but is a #{value.class}"
|
161
|
+
when Symbol
|
162
|
+
"\n- Expected #{value.inspect} to respond to :#{expected}"
|
163
|
+
when Proc
|
164
|
+
"\n- Expected #{value.inspect} to return a truthy value for proc #{expected}"
|
165
|
+
when Regexp
|
166
|
+
"\n- Expected stringified #{value.inspect} to match #{expected.inspect}"
|
167
|
+
when Range
|
168
|
+
"\n- Expected #{value.inspect} to be included in #{expected.inspect}"
|
169
|
+
when true
|
170
|
+
"\n- Expected #{value.inspect} to be truthy"
|
171
|
+
when false
|
172
|
+
"\n- Expected #{value.inspect} to be falsy"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
data/lib/sig/kernel.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kernel
|
2
|
+
private
|
3
|
+
|
4
|
+
# Defines a method signature for a method on this object:
|
5
|
+
#
|
6
|
+
# sig [:to_i, :to_i], Integer,
|
7
|
+
# def sum(a, b)
|
8
|
+
# a.to_i + b.to_i
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
def sig(expected_arguments, expected_result = nil, method_name)
|
12
|
+
if is_a?(Module)
|
13
|
+
Sig.define(self, expected_arguments, expected_result, method_name)
|
14
|
+
else
|
15
|
+
sig_self(expected_arguments, expected_result, method_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines a method signature for a method on this object's singleton class
|
20
|
+
#
|
21
|
+
# sig_self [:to_i, :to_i], Integer,
|
22
|
+
# def self.sum(a, b)
|
23
|
+
# a.to_i + b.to_i
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
def sig_self(expected_arguments, expected_result = nil, method_name)
|
27
|
+
Sig.define(singleton_class, expected_arguments, expected_result, method_name)
|
28
|
+
end
|
29
|
+
end
|
data/lib/sig/none.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# require "sig/none" instead of "sig" to get fake methods that do nothing
|
2
|
+
|
3
|
+
require_relative '../charyf_sig'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
private
|
7
|
+
|
8
|
+
def sig(_, _ = nil, method_name)
|
9
|
+
method_name
|
10
|
+
end
|
11
|
+
#
|
12
|
+
def sig_self(_, _ = nil, method_name)
|
13
|
+
method_name
|
14
|
+
end
|
15
|
+
end
|
data/lib/sig/version.rb
ADDED
data/sig.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/lib/sig/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "charyf_sig"
|
7
|
+
gem.version = Sig::VERSION
|
8
|
+
gem.summary = "Optional Type Assertions for Ruby."
|
9
|
+
gem.description = "Optional Type Assertions for Ruby methods."
|
10
|
+
gem.authors = ["Richard Ludvigh"]
|
11
|
+
gem.email = "richard@ludvigh.sk"
|
12
|
+
gem.homepage = "https://github.com/Charyf/sig/"
|
13
|
+
gem.license = "MIT"
|
14
|
+
|
15
|
+
gem.files = Dir["{**/}{.*,*}"].select{ |path| File.file?(path) && path !~ /^pkg/ }
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.required_ruby_version = "~> 2.1"
|
21
|
+
end
|
data/spec/sig_spec.rb
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
require_relative "../lib/charyf_sig"
|
2
|
+
require "minitest/autorun"
|
3
|
+
require "minitest/reporters"
|
4
|
+
Minitest::Reporters.use! Minitest::Reporters::ProgressReporter.new
|
5
|
+
|
6
|
+
|
7
|
+
describe Sig do
|
8
|
+
let(:instance){ klass.new }
|
9
|
+
let(:klass){
|
10
|
+
Class.new do
|
11
|
+
def my_method(object)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sum(a, b)
|
15
|
+
a + b
|
16
|
+
end
|
17
|
+
|
18
|
+
def key(arg, word: 42)
|
19
|
+
end
|
20
|
+
|
21
|
+
def priv
|
22
|
+
end
|
23
|
+
private :priv
|
24
|
+
|
25
|
+
def prot
|
26
|
+
end
|
27
|
+
protected :prot
|
28
|
+
|
29
|
+
def self.mul(a, b)
|
30
|
+
a * b
|
31
|
+
end
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
describe "Kernel#sig" do
|
37
|
+
describe "self is no Module" do
|
38
|
+
it "will call Sig.define(...) for self's singleton_class" do
|
39
|
+
sig [String],
|
40
|
+
def bla(argument)
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_raises Sig::ArgumentTypeError do
|
44
|
+
bla 42
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "self if a Module" do
|
50
|
+
it "will call Sig.define(...) for self" do
|
51
|
+
class Klass
|
52
|
+
sig [String],
|
53
|
+
def bla(argument)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_raises Sig::ArgumentTypeError do
|
58
|
+
Klass.new.bla 42
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "sig_self" do
|
65
|
+
it "will always call Sig.define(...) on self's singleton_class" do
|
66
|
+
class Klass
|
67
|
+
sig_self [String],
|
68
|
+
def self.blubb(argument)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_raises Sig::ArgumentTypeError do
|
73
|
+
Klass.blubb 42
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "Types" do
|
79
|
+
describe "Module" do
|
80
|
+
it "will do nothing if value is kind of module" do
|
81
|
+
Sig.define klass, [Numeric], :my_method
|
82
|
+
instance.my_method(42)
|
83
|
+
assert true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "will raise if value is not kind of module" do
|
87
|
+
Sig.define klass, [Array], :my_method
|
88
|
+
assert_raises Sig::ArgumentTypeError do
|
89
|
+
instance.my_method(42)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "String" do
|
95
|
+
it "will cast the string to constant and validate" do
|
96
|
+
module A
|
97
|
+
class B
|
98
|
+
module C
|
99
|
+
class D
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
Sig.define klass, ['A::B::C::D'], :my_method
|
106
|
+
instance.my_method(A::B::C::D.new)
|
107
|
+
assert true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "will validate the class" do
|
111
|
+
Sig.define klass, ['String'], :my_method
|
112
|
+
assert_raises Sig::ArgumentTypeError do
|
113
|
+
instance.my_method(:symbol)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "fails on cast when non existant class is present during runtime" do
|
118
|
+
module A
|
119
|
+
class B
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
Sig.define klass, [['A::B', 'A::C']], :my_method
|
125
|
+
|
126
|
+
assert_raises NameError do
|
127
|
+
instance.my_method(A::B.new)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "Symbol" do
|
133
|
+
it "will do nothing if value is kind of module" do
|
134
|
+
Sig.define klass, [:to_i], :my_method
|
135
|
+
instance.my_method("string")
|
136
|
+
assert true
|
137
|
+
end
|
138
|
+
|
139
|
+
it "will raise if value does not respond to method named by the symbol" do
|
140
|
+
Sig.define klass, [:to_i], :my_method
|
141
|
+
assert_raises Sig::ArgumentTypeError do
|
142
|
+
instance.my_method(true)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "Proc" do
|
148
|
+
it "will do nothing if value does return a falsy result after being processed by the proc" do
|
149
|
+
Sig.define klass, [->(e){ e.odd? }], :my_method
|
150
|
+
instance.my_method(43)
|
151
|
+
assert true
|
152
|
+
end
|
153
|
+
|
154
|
+
it "will raise if value does return a truthy result after being processed by the proc" do
|
155
|
+
Sig.define klass, [->(e){ e.odd? }], :my_method
|
156
|
+
assert_raises Sig::ArgumentTypeError do
|
157
|
+
instance.my_method(42)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "Regexpg" do
|
163
|
+
it "will do nothing if stringified value does match" do
|
164
|
+
Sig.define klass, [/bla/], :my_method
|
165
|
+
instance.my_method("bla")
|
166
|
+
assert true
|
167
|
+
end
|
168
|
+
|
169
|
+
it "will raise if stringified value does not match" do
|
170
|
+
Sig.define klass, [/bla/], :my_method
|
171
|
+
assert_raises Sig::ArgumentTypeError do
|
172
|
+
instance.my_method("blubb")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "Range" do
|
178
|
+
it "will do nothing if included in range" do
|
179
|
+
Sig.define klass, [1...100], :my_method
|
180
|
+
instance.my_method(42)
|
181
|
+
assert true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "will raise if not included in range" do
|
185
|
+
Sig.define klass, [1...100], :my_method
|
186
|
+
assert_raises Sig::ArgumentTypeError do
|
187
|
+
instance.my_method("blubb")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "true" do
|
193
|
+
it "will do nothing if value is truthy" do
|
194
|
+
Sig.define klass, [true], :my_method
|
195
|
+
instance.my_method(42)
|
196
|
+
assert true
|
197
|
+
end
|
198
|
+
|
199
|
+
it "will raise if value is not truthy" do
|
200
|
+
Sig.define klass, [true], :my_method
|
201
|
+
assert_raises Sig::ArgumentTypeError do
|
202
|
+
instance.my_method(nil)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "false" do
|
208
|
+
it "will do nothing if value is falsy" do
|
209
|
+
Sig.define klass, [false], :my_method
|
210
|
+
instance.my_method(nil)
|
211
|
+
assert true
|
212
|
+
end
|
213
|
+
|
214
|
+
it "will raise if value is not falsy" do
|
215
|
+
Sig.define klass, [false], :my_method
|
216
|
+
assert_raises Sig::ArgumentTypeError do
|
217
|
+
instance.my_method(42)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "nil" do
|
223
|
+
it "will never raise" do
|
224
|
+
Sig.define klass, [nil], :my_method
|
225
|
+
instance.my_method(42)
|
226
|
+
assert true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "Formats" do
|
232
|
+
it "checks both parameters" do
|
233
|
+
Sig.define klass, [Integer, Float], :sum
|
234
|
+
assert_raises Sig::ArgumentTypeError do
|
235
|
+
instance.sum(42, 42)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
it "will check the result if another parameter is given to sig" do
|
240
|
+
Sig.define klass, [Integer, Integer], Float, :sum
|
241
|
+
assert_raises Sig::ResultTypeError do
|
242
|
+
instance.sum(42, 42)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
it "is possible to only check for result type" do
|
247
|
+
Sig.define klass, nil, Float, :sum
|
248
|
+
assert_raises Sig::ResultTypeError do
|
249
|
+
instance.sum(42, 42)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
it "is possible to check for one of multiple given types" do
|
254
|
+
Sig.define klass, [[Numeric, String]], :my_method
|
255
|
+
instance.my_method(42)
|
256
|
+
assert true
|
257
|
+
end
|
258
|
+
|
259
|
+
describe "Keyword Arguments" do
|
260
|
+
it "works" do
|
261
|
+
Sig.define klass, {word: String}, :key
|
262
|
+
assert_raises Sig::ArgumentTypeError do
|
263
|
+
instance.key(nil, word: 42)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
it "works with mixed positionial and keyword parameters" do
|
268
|
+
Sig.define klass, [Numeric, {word: String}], :key
|
269
|
+
assert_raises Sig::ArgumentTypeError do
|
270
|
+
instance.key(42, word: 43)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
it "works with mixed positionial and keyword parameters 2" do
|
275
|
+
Sig.define klass, [Numeric, {word: String}], :key
|
276
|
+
assert_raises Sig::ArgumentTypeError do
|
277
|
+
instance.key("42", word: "43")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "Implementation Details" do
|
284
|
+
it "can be used on instance level" do
|
285
|
+
Sig.define klass, [Numeric], :sum
|
286
|
+
assert_raises Sig::ArgumentTypeError do
|
287
|
+
instance.sum("str", "ing")
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it "can be used on class level" do
|
292
|
+
Sig.define klass.singleton_class, [Numeric], :mul
|
293
|
+
assert_raises Sig::ArgumentTypeError do
|
294
|
+
klass.mul("str", "ing")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
it "defines an anomynous signature checker module" do
|
299
|
+
Sig.define klass, [Numeric], :sum
|
300
|
+
assert_equal Module, klass.instance_variable_get(:@_sig).class
|
301
|
+
end
|
302
|
+
|
303
|
+
it "uses the same signature module for multiple signatures" do
|
304
|
+
Sig.define klass, [Numeric], :sum
|
305
|
+
Sig.define klass, [Numeric], :my_method
|
306
|
+
assert_equal 2, klass.instance_variable_get(:@_sig).instance_methods.size
|
307
|
+
end
|
308
|
+
|
309
|
+
it "does not define signature modules if no signature is used in the class" do
|
310
|
+
assert_equal nil, klass.instance_variable_get(:@_sig)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "respects restricted visibility of private methods" do
|
314
|
+
Sig.define klass, [String], :priv
|
315
|
+
assert_raises NoMethodError do
|
316
|
+
instance.priv
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
it "respects restricted visibility of protected methods" do
|
321
|
+
Sig.define klass, [String], :prot
|
322
|
+
assert_raises NoMethodError do
|
323
|
+
instance.prot
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "Wrong Usage" do
|
329
|
+
it "will raise an ArgumentError if trying to define a signature for an unknown method" do
|
330
|
+
assert_raises ArgumentError do
|
331
|
+
Sig.define klass, [String], :unknown
|
332
|
+
end
|
333
|
+
end
|
334
|
+
it "will raise an ArgumentError if unknown signature types are used" do
|
335
|
+
assert_raises ArgumentError do
|
336
|
+
Sig.define klass, [Object.new], :my_method
|
337
|
+
klass.new.my_method(42)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: charyf_sig
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Ludvigh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-18 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Optional Type Assertions for Ruby methods.
|
14
|
+
email: richard@ludvigh.sk
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".gitignore"
|
20
|
+
- ".travis.yml"
|
21
|
+
- CHANGELOG.md
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- MIT-LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- benchmark/compare.rb
|
28
|
+
- lib/charyf_sig.rb
|
29
|
+
- lib/sig/kernel.rb
|
30
|
+
- lib/sig/none.rb
|
31
|
+
- lib/sig/version.rb
|
32
|
+
- sig.gemspec
|
33
|
+
- spec/sig_spec.rb
|
34
|
+
homepage: https://github.com/Charyf/sig/
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.1'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.6.8
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Optional Type Assertions for Ruby.
|
58
|
+
test_files:
|
59
|
+
- spec/sig_spec.rb
|