charyf_sig 1.1.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: ed8055a7e00c0933a4d92636952405c230ccd016
4
+ data.tar.gz: 7f7d37e7b0f432b9c3e18d809101029c27d982dd
5
+ SHA512:
6
+ metadata.gz: 9575a6279b50b6fd194e4f1f7045bb78732039f5843c05cde34e15501af1598b610903c6431164e06f5d9973903c8e40d1e25470d1aa5bfad1a40f8b27248694
7
+ data.tar.gz: 3f80d4950303c93991e5a219b6ae9414b42a935602f2d99e51cad925606a5d2af4ec1cabbd32b1225262465fc069ae4132f2338eb6ae66946405fa28abc41b5f
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ .idea
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
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
+ - ruby-head
11
+ - jruby-9000
12
+
13
+ cache:
14
+ - bundler
15
+
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: ruby-head
19
+ - rvm: jruby-9000
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ ## CHANGELOG
2
+
3
+ ### 1.0.1
4
+
5
+ * Improve keyword argument code, which also improves performance
6
+
7
+ ### 1.0.0
8
+
9
+ * Inital release
10
+
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'minitest'
7
+ gem 'minitest-line'
8
+ gem 'minitest-reporters'
9
+ # gem 'benchmark-ips'
10
+ # gem 'rubype'
11
+ # gem 'contracts'
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
@@ -0,0 +1,4 @@
1
+ module Sig
2
+ VERSION = "1.1.0".freeze
3
+ end
4
+
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