sig 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4715821434d81f8b6d4473888baf83db09dea536
4
+ data.tar.gz: f99a5268d57adfac1a8acce982998c7c317ab430
5
+ SHA512:
6
+ metadata.gz: ab71526170db48fed397b21f07a8905c7cdab0d2faaec363d2d58d2516a9253a3b7345cc2212d757db5cb51d44d7e9331a81a66886cdaa27f95b0e3a3b5f7f42
7
+ data.tar.gz: 91e5c513bb67eba3f0a17d1fb549091608fb003062f76e18b2607716c8a003dc876ab9f8ae4654f91e80a33fdaba9dde04db04b411762664e123d9795266f310
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ sudo: false
2
+ language: ruby
3
+
4
+ script: bundle exec ruby spec/sig_spec.rb
5
+
6
+ rvm:
7
+ - 2.2
8
+ - 2.1
9
+ - rbx-2
10
+ - jruby-9000
11
+
12
+ cache:
13
+ - bundler
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: jruby-9000
data/B.rev ADDED
File without changes
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## CHANGELOG
2
+
3
+ ### 1.0.0
4
+
5
+ * Inital release
6
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'minitest'
6
+ gem 'minitest-line'
7
+ gem 'minitest-reporters'
8
+ gem 'benchmark-ips'
9
+ gem 'rubype'
10
+ gem 'contracts'
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sig (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ benchmark-ips (2.1.1)
11
+ builder (3.2.2)
12
+ contracts (0.8)
13
+ minitest (5.6.0)
14
+ minitest-line (0.6.2)
15
+ minitest (~> 5.0)
16
+ minitest-reporters (1.0.11)
17
+ ansi
18
+ builder
19
+ minitest (>= 5.0)
20
+ ruby-progressbar
21
+ ruby-progressbar (1.7.5)
22
+ rubype (0.2.6)
23
+
24
+ PLATFORMS
25
+ java
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ benchmark-ips
30
+ contracts
31
+ minitest
32
+ minitest-line
33
+ minitest-reporters
34
+ rubype
35
+ sig!
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,113 @@
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
+ A.new.mul(4,"3")
24
+ # Sig::ArgumentTypeError:
25
+ # - Expected "3" to be a Numeric, but is a String
26
+
27
+
28
+ # Explicitely define signature for singleton_class
29
+ class B
30
+ sig_self [:reverse],
31
+ def self.rev(object)
32
+ object.reverse
33
+ end
34
+ end
35
+ B.rev 42
36
+ # Sig::ArgumentTypeError:
37
+ # - Expected 42 to respond to :reverse
38
+ ```
39
+
40
+ 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.
41
+
42
+ ## Features & Design Goals
43
+ * Provide an intuitive way to define signatures
44
+ * Only do argument/result type checks, nothing else
45
+ * Use Ruby's inheritance chain, don't redefine methods
46
+ * Encourage duck typing
47
+ * Should work with keyword arguments
48
+ * Only target Ruby 2.1+
49
+
50
+ ### This is not static typing. Ruby is a dynamic language:
51
+
52
+ Nevertheless, nothing is wrong with ensuring specific behaviour of method arguments when you need it.
53
+
54
+ ### Is this better than rubype?
55
+
56
+ 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. The performance impact is quite similar (sig version 1.0 vs rubype 0.2.5), run `rake benchmark` for details.
57
+
58
+ ## Setup
59
+
60
+ Add to your `Gemfile`:
61
+
62
+ ```ruby
63
+ gem 'sig'
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ See example at top for basic usage.
69
+
70
+ ### Supported Behavior Types
71
+
72
+ You can use the following behavior types in the signature definition:
73
+
74
+ Type | Meaning
75
+ ------- | -------
76
+ Symbol | Argument must respond to a method with this name
77
+ Module | Argument must be of this module
78
+ Array | Argument can be of any type found in the array
79
+ true | Argument must be truthy
80
+ false | Argument must be falsy
81
+ nil | Wildcard for any argument
82
+
83
+ ### Example Signatures
84
+
85
+ ```ruby
86
+ sig [:to_i], Numeric, # takes any object that responds to :to_i as argument, numeric result
87
+ sig [Numeric], String, # one numeric argument, string result
88
+ sig [Numeric, Numeric], String, # two numeric arguments, string result
89
+ sig [:to_s, :to_s], # two arguments that support :to_s, don't care about result
90
+ sig nil, String, # don't care about arguments, as long result is string
91
+ sig {keyword: Integer} # keyword argument must be an intieger
92
+ sig [:to_f, {keyword: String}], # mixing positional and keyword arguments is possible
93
+ sig [[Numeric, NilClass]], Float # one argument that must nil or numeric, result must be float
94
+ sig [Numeric, nil, Numeric], # first and third argument must be numeric, don't care about type of second
95
+ ```
96
+
97
+ 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.
98
+
99
+ ## Deactivate all signature checking
100
+
101
+ ```ruby
102
+ require 'sig/none' # instead of require 'sig'
103
+ ```
104
+
105
+ ## Alternatives for type checking and more
106
+
107
+ - https://github.com/gogotanaka/Rubype
108
+ - https://github.com/egonSchiele/contracts.ruby
109
+ - https://github.com/plum-umd/rtc
110
+
111
+ ## MIT License
112
+
113
+ Copyright (C) 2015 Jan Lelis <http://janlelis.com>. Released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,139 @@
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
+ # Benchmark: Take with a grain of salt
34
+
35
+ desc "Compare with contracts and rubype"
36
+ task :benchmark do
37
+ require "benchmark/ips"
38
+ require "rubype"
39
+ require "rubype/version"
40
+ require "contracts"
41
+ require "contracts/version"
42
+ require_relative "lib/sig"
43
+
44
+ # - - -
45
+
46
+ class PureSum
47
+ def sum(x, y)
48
+ x + y
49
+ end
50
+
51
+ def mul(x, y)
52
+ x * y
53
+ end
54
+ end
55
+ pure_instance = PureSum.new
56
+ puts "ruby version: #{RUBY_VERSION}"
57
+
58
+ # - - -
59
+
60
+ class SigSum
61
+ sig [Numeric, Numeric], Numeric,
62
+ def sum(x, y)
63
+ x + y
64
+ end
65
+
66
+ sig [Numeric, Numeric], Numeric,
67
+ def mul(x, y)
68
+ x * y
69
+ end
70
+ end
71
+ sig_instance = SigSum.new
72
+ puts "sig version: #{Sig::VERSION}"
73
+
74
+ # - - -
75
+
76
+ class RubypeSum
77
+ def sum(x, y)
78
+ x + y
79
+ end
80
+ typesig :sum, [Numeric, Numeric] => Numeric
81
+
82
+ def mul(x, y)
83
+ x * y
84
+ end
85
+ typesig :mul, [Numeric, Numeric] => Numeric
86
+ end
87
+ rubype_instance = RubypeSum.new
88
+ puts "rubype version: #{Rubype::VERSION}"
89
+
90
+ # - - -
91
+
92
+ class ContractsSum
93
+ include Contracts
94
+
95
+ Contract Num, Num => Num
96
+ def sum(x, y)
97
+ x + y
98
+ end
99
+
100
+ Contract Num, Num => Num
101
+ def mul(x, y)
102
+ x * y
103
+ end
104
+ end
105
+ contracts_instance = ContractsSum.new
106
+ puts "contracts version: #{Contracts::VERSION}"
107
+
108
+ Benchmark.ips do |x|
109
+ x.report("pure"){
110
+ pure_instance.sum(1, 2)
111
+ pure_instance.mul(1, 2)
112
+ }
113
+
114
+ x.report("sig"){
115
+ sig_instance.sum(1, 2)
116
+ sig_instance.mul(1, 2)
117
+ }
118
+
119
+ x.report("rubype"){
120
+ rubype_instance.sum(1, 2)
121
+ rubype_instance.mul(1, 2)
122
+ }
123
+
124
+ x.report("contracts"){
125
+ contracts_instance.sum(1, 2)
126
+ contracts_instance.mul(1, 2)
127
+ }
128
+ x.warmup = 10
129
+ x.compare!
130
+ end
131
+ end
132
+
133
+ # # #
134
+ # Specs
135
+ desc "Run specs"
136
+ task :spec do
137
+ ruby "spec/sig_spec.rb"
138
+ end
139
+ task default: :spec
Binary file
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 '../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
@@ -0,0 +1,4 @@
1
+ module Sig
2
+ VERSION = "1.0.0".freeze
3
+ end
4
+
data/lib/sig.rb ADDED
@@ -0,0 +1,141 @@
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
+ method_visibility = get_method_visibility_or_raise(object, method_name)
13
+ signature_checker = get_or_create_signature_checker(object)
14
+ signature_checker.send :define_method, method_name do |*arguments, **keyword_arguments|
15
+ ::Sig.check_arguments(expected_arguments, arguments, keyword_arguments)
16
+ if keyword_arguments.empty?
17
+ result = super(*arguments)
18
+ else
19
+ result = super(*arguments, **keyword_arguments)
20
+ end
21
+ ::Sig.check_result(expected_result, result)
22
+
23
+ result
24
+ end
25
+ signature_checker.send(method_visibility, method_name)
26
+
27
+ method_name
28
+ end
29
+
30
+ def self.get_method_visibility_or_raise(object, method_name)
31
+ case
32
+ when object.private_method_defined?(method_name)
33
+ :private
34
+ when object.protected_method_defined?(method_name)
35
+ :protected
36
+ when object.public_method_defined?(method_name)
37
+ :public
38
+ else
39
+ raise ArgumentError, "No method with name :#{method_name} for object #{object.inspect}"
40
+ end
41
+ end
42
+
43
+ def self.get_or_create_signature_checker(object)
44
+ unless checker = object.instance_variable_get(:@_sig)
45
+ checker = object.instance_variable_set(:@_sig, Module.new)
46
+ def checker.inspect() "#<Sig:#{object_id}>" end
47
+ object.prepend(checker)
48
+ end
49
+
50
+ checker
51
+ end
52
+
53
+ def self.check_arguments(expected_arguments, arguments, keyword_arguments)
54
+ errors = ""
55
+
56
+ normalized_expected_arguments = Array(expected_arguments).dup
57
+ if normalized_expected_arguments.last.is_a?(Hash)
58
+ expected_keyword_arguments = normalized_expected_arguments.delete_at(-1)
59
+ elsif keyword_arguments
60
+ expected_keyword_arguments = {}
61
+ arguments += [keyword_arguments] unless keyword_arguments.empty?
62
+ end
63
+
64
+ arguments.each_with_index{ |argument, index|
65
+ if error = valid_or_formatted_error(normalized_expected_arguments[index], argument)
66
+ errors << error
67
+ end
68
+ }
69
+
70
+ unless expected_keyword_arguments.empty?
71
+ keyword_arguments.each{ |key, argument|
72
+ if error = valid_or_formatted_error(expected_keyword_arguments[key], argument)
73
+ errors << error
74
+ end
75
+ }
76
+ end
77
+
78
+ unless errors.empty?
79
+ raise ArgumentTypeError, errors
80
+ end
81
+ end
82
+
83
+ def self.check_result(expected_result, result)
84
+ unless matches? expected_result, result
85
+ raise ResultTypeError, format_error(expected_result, result)
86
+ end
87
+ end
88
+
89
+ def self.matches?(expected, value)
90
+ case expected
91
+ when Array
92
+ expected.any?{ |expected_element| matches? expected_element, value }
93
+ when Module
94
+ value.is_a?(expected)
95
+ when Symbol
96
+ value.respond_to?(expected)
97
+ when Proc
98
+ !!expected.call(value)
99
+ when Regexp
100
+ !!(expected =~ String(value))
101
+ when Range
102
+ expected.include?(value)
103
+ when true
104
+ !!value
105
+ when false
106
+ !value
107
+ when nil
108
+ true
109
+ else
110
+ raise ArgumentError, "Invalid signature definition: Unknown behavior #{expected}"
111
+ end
112
+ end
113
+
114
+ def self.valid_or_formatted_error(expected_argument, argument)
115
+ if !expected_argument.nil? && !matches?(expected_argument, argument)
116
+ format_error(expected_argument, argument)
117
+ end
118
+ end
119
+
120
+ def self.format_error(expected, value)
121
+ case expected
122
+ when Array
123
+ expected.map{ |expected_element| format_error(expected_element, value) }*" OR"
124
+ when Module
125
+ "\n- Expected #{value.inspect} to be a #{expected}, but is a #{value.class}"
126
+ when Symbol
127
+ "\n- Expected #{value.inspect} to respond to :#{expected}"
128
+ when Proc
129
+ "\n- Expected #{value.inspect} to return a truthy value for proc #{expected}"
130
+ when Regexp
131
+ "\n- Expected stringified #{value.inspect} to match #{expected.inspect}"
132
+ when Range
133
+ "\n- Expected #{value.inspect} to be included in #{expected.inspect}"
134
+ when true
135
+ "\n- Expected #{value.inspect} to be truthy"
136
+ when false
137
+ "\n- Expected #{value.inspect} to be falsy"
138
+ end
139
+ end
140
+ end
141
+
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 = "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 = ["Jan Lelis"]
11
+ gem.email = "mail@janlelis.de"
12
+ gem.homepage = "https://github.com/janlelis/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
Binary file
data/spec/sig_spec.rb ADDED
@@ -0,0 +1,304 @@
1
+ require_relative "../lib/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 "Symbol" do
95
+ it "will do nothing if value is kind of module" do
96
+ Sig.define klass, [:to_i], :my_method
97
+ instance.my_method("string")
98
+ assert true
99
+ end
100
+
101
+ it "will raise if value does not respond to method named by the symbol" do
102
+ Sig.define klass, [:to_i], :my_method
103
+ assert_raises Sig::ArgumentTypeError do
104
+ instance.my_method(true)
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "Proc" do
110
+ it "will do nothing if value does return a falsy result after being processed by the proc" do
111
+ Sig.define klass, [->(e){ e.odd? }], :my_method
112
+ instance.my_method(43)
113
+ assert true
114
+ end
115
+
116
+ it "will raise if value does return a truthy result after being processed by the proc" do
117
+ Sig.define klass, [->(e){ e.odd? }], :my_method
118
+ assert_raises Sig::ArgumentTypeError do
119
+ instance.my_method(42)
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "Regexpg" do
125
+ it "will do nothing if stringified value does match" do
126
+ Sig.define klass, [/bla/], :my_method
127
+ instance.my_method("bla")
128
+ assert true
129
+ end
130
+
131
+ it "will raise if stringified value does not match" do
132
+ Sig.define klass, [/bla/], :my_method
133
+ assert_raises Sig::ArgumentTypeError do
134
+ instance.my_method("blubb")
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "Range" do
140
+ it "will do nothing if included in range" do
141
+ Sig.define klass, [1...100], :my_method
142
+ instance.my_method(42)
143
+ assert true
144
+ end
145
+
146
+ it "will raise if not included in range" do
147
+ Sig.define klass, [1...100], :my_method
148
+ assert_raises Sig::ArgumentTypeError do
149
+ instance.my_method("blubb")
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "true" do
155
+ it "will do nothing if value is truthy" do
156
+ Sig.define klass, [true], :my_method
157
+ instance.my_method(42)
158
+ assert true
159
+ end
160
+
161
+ it "will raise if value is not truthy" do
162
+ Sig.define klass, [true], :my_method
163
+ assert_raises Sig::ArgumentTypeError do
164
+ instance.my_method(nil)
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "false" do
170
+ it "will do nothing if value is falsy" do
171
+ Sig.define klass, [false], :my_method
172
+ instance.my_method(nil)
173
+ assert true
174
+ end
175
+
176
+ it "will raise if value is not falsy" do
177
+ Sig.define klass, [false], :my_method
178
+ assert_raises Sig::ArgumentTypeError do
179
+ instance.my_method(42)
180
+ end
181
+ end
182
+ end
183
+
184
+ describe "nil" do
185
+ it "will never raise" do
186
+ Sig.define klass, [nil], :my_method
187
+ instance.my_method(42)
188
+ assert true
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "Formats" do
194
+ it "checks both parameters" do
195
+ Sig.define klass, [Integer, Float], :sum
196
+ assert_raises Sig::ArgumentTypeError do
197
+ instance.sum(42, 42)
198
+ end
199
+ end
200
+
201
+ it "will check the result if another parameter is given to sig" do
202
+ Sig.define klass, [Integer, Integer], Float, :sum
203
+ assert_raises Sig::ResultTypeError do
204
+ instance.sum(42, 42)
205
+ end
206
+ end
207
+
208
+ it "is possible to only check for result type" do
209
+ Sig.define klass, nil, Float, :sum
210
+ assert_raises Sig::ResultTypeError do
211
+ instance.sum(42, 42)
212
+ end
213
+ end
214
+
215
+ it "is possible to check for one of multiple given types" do
216
+ Sig.define klass, [[Numeric, String]], :my_method
217
+ instance.my_method(42)
218
+ assert true
219
+ end
220
+
221
+ describe "Keyword Arguments" do
222
+ it "works" do
223
+ Sig.define klass, {word: String}, :key
224
+ assert_raises Sig::ArgumentTypeError do
225
+ instance.key(nil, word: 42)
226
+ end
227
+ end
228
+
229
+ it "works with mixed positionial and keyword parameters" do
230
+ Sig.define klass, [Numeric, {word: String}], :key
231
+ assert_raises Sig::ArgumentTypeError do
232
+ instance.key(42, word: 43)
233
+ end
234
+ end
235
+
236
+ it "works with mixed positionial and keyword parameters 2" do
237
+ Sig.define klass, [Numeric, {word: String}], :key
238
+ assert_raises Sig::ArgumentTypeError do
239
+ instance.key("42", word: "43")
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ describe "Implementation Details" do
246
+ it "can be used on instance level" do
247
+ Sig.define klass, [Numeric], :sum
248
+ assert_raises Sig::ArgumentTypeError do
249
+ instance.sum("str", "ing")
250
+ end
251
+ end
252
+
253
+ it "can be used on class level" do
254
+ Sig.define klass.singleton_class, [Numeric], :mul
255
+ assert_raises Sig::ArgumentTypeError do
256
+ klass.mul("str", "ing")
257
+ end
258
+ end
259
+
260
+ it "defines an anomynous signature checker module" do
261
+ Sig.define klass, [Numeric], :sum
262
+ assert_equal Module, klass.instance_variable_get(:@_sig).class
263
+ end
264
+
265
+ it "uses the same signature module for multiple signatures" do
266
+ Sig.define klass, [Numeric], :sum
267
+ Sig.define klass, [Numeric], :my_method
268
+ assert_equal 2, klass.instance_variable_get(:@_sig).instance_methods.size
269
+ end
270
+
271
+ it "does not define signature modules if no signature is used in the class" do
272
+ assert_equal nil, klass.instance_variable_get(:@_sig)
273
+ end
274
+
275
+ it "respects restricted visibility of private methods" do
276
+ Sig.define klass, [String], :priv
277
+ assert_raises NoMethodError do
278
+ instance.priv
279
+ end
280
+ end
281
+
282
+ it "respects restricted visibility of protected methods" do
283
+ Sig.define klass, [String], :prot
284
+ assert_raises NoMethodError do
285
+ instance.prot
286
+ end
287
+ end
288
+ end
289
+
290
+ describe "Wrong Usage" do
291
+ it "will raise an ArgumentError if trying to define a signature for an unknown method" do
292
+ assert_raises ArgumentError do
293
+ Sig.define klass, [String], :unknown
294
+ end
295
+ end
296
+ it "will raise an ArgumentError if unknown signature types are used" do
297
+ assert_raises ArgumentError do
298
+ Sig.define klass, [Object.new], :my_method
299
+ klass.new.my_method(42)
300
+ end
301
+ end
302
+ end
303
+ end
304
+
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sig
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Lelis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Optional Type Assertions for Ruby methods.
14
+ email: mail@janlelis.de
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".gitignore"
20
+ - ".travis.yml"
21
+ - B.rev
22
+ - CHANGELOG.md
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - MIT-LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - lib/sig.rb
29
+ - lib/sig/.kernel.rb.swp
30
+ - lib/sig/kernel.rb
31
+ - lib/sig/none.rb
32
+ - lib/sig/version.rb
33
+ - sig.gemspec
34
+ - spec/.sig_spec.rb.swp
35
+ - spec/sig_spec.rb
36
+ homepage: https://github.com/janlelis/sig
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.1'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.4.6
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Optional Type Assertions for Ruby.
60
+ test_files:
61
+ - spec/.sig_spec.rb.swp
62
+ - spec/sig_spec.rb
63
+ has_rdoc: