antidote-types 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +85 -0
- data/lib/antidote.rb +85 -0
- data/lib/antidote/return_type_error.rb +12 -0
- data/lib/antidote/variable_type_error.rb +12 -0
- data/lib/antidote/version.rb +3 -0
- data/spec/antidote_spec.rb +45 -0
- data/spec/spec_helper.rb +40 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 80a6d4abd0f6d721c3930e49e871b176c9344ef2
|
4
|
+
data.tar.gz: 1f96bc904f5e4ccc894fdcbb213b90e7e3ece3c5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9fe3ce19d7d26e89ad543e35e967d2a261fc78417d045fbb8ce4b7219efa66a12c4aca3eb24b02ada204115d94c25ae97ff217916b84c2a86889c6305255c5b4
|
7
|
+
data.tar.gz: 38c3acf4a50fd93b009550697b835c745fa58903607a31709232145733ded57c00ac3e3e269ef494e21c0bc8addffa9dc394231e88352266e65253017352bafd
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Mathias Jean Johansen <mathias@mjj.io>
|
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 NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Antidote
|
2
|
+
Antidote is a small, highly experimental library that performs runtime type
|
3
|
+
assertions in Ruby in the same vein as
|
4
|
+
[Rubype](https://github.com/gogotanaka/Rubype),
|
5
|
+
[contracts.ruby](https://github.com/egonSchiele/contracts.ruby),
|
6
|
+
[sig](https://github.com/janlelis/sig), and so on. This repository should mainly
|
7
|
+
be considered a proof of concept.
|
8
|
+
|
9
|
+
It relies heavily on metaprogramming, and it is unsafe for method calls with
|
10
|
+
side-effects. Nonetheless, it works.
|
11
|
+
|
12
|
+
Antidote exposes two methods: `annotate`, and `annotate_class_method` which can
|
13
|
+
be used a such:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require_relative 'lib/antidote'
|
17
|
+
|
18
|
+
class Adder
|
19
|
+
include Antidote
|
20
|
+
|
21
|
+
annotate Fixnum, Fixnum, Fixnum do
|
22
|
+
def add(x, y)
|
23
|
+
x + y
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
adder = Adder.new.add(1, 2)
|
29
|
+
```
|
30
|
+
|
31
|
+
Here, `annotate Fixnum, Fixnum, Fixnum` means that we expect two `Fixnum` as
|
32
|
+
input, and we should return a `Fixnum` as result.
|
33
|
+
|
34
|
+
This program returns 3 as expected since we comply with the type signature, but
|
35
|
+
if we change `x` to be a `String`, we receive the following error message:
|
36
|
+
|
37
|
+
```
|
38
|
+
Variable `x` in method `add` expected a String, but received a Fixnum
|
39
|
+
(Antidote::VariableTypeError)
|
40
|
+
```
|
41
|
+
|
42
|
+
If we change the return type, we'll see a similar message:
|
43
|
+
|
44
|
+
```
|
45
|
+
Expected `add` to return a String, but it returned a Fixnum instead
|
46
|
+
(Antidote::ReturnTypeError)
|
47
|
+
```
|
48
|
+
|
49
|
+
You can obviously handle both `Antidote::VariableTypeError` and
|
50
|
+
`Antidote::ReturnTypeError` in your code if needed.
|
51
|
+
|
52
|
+
Please, visit [`example.rb`](example.rb) for a full example.
|
53
|
+
|
54
|
+
## Requirements
|
55
|
+
* Ruby 2.2.0 or newer.
|
56
|
+
|
57
|
+
## Installation
|
58
|
+
Add this line to your Gemfile:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
gem 'antidote-types'
|
62
|
+
```
|
63
|
+
|
64
|
+
And then run:
|
65
|
+
|
66
|
+
```
|
67
|
+
bundle
|
68
|
+
```
|
69
|
+
|
70
|
+
Or simply install it yourself:
|
71
|
+
|
72
|
+
```
|
73
|
+
gem install antidote-types
|
74
|
+
```
|
75
|
+
|
76
|
+
## Contribute
|
77
|
+
1. [Fork it](https://github.com/majjoha/antidote/fork).
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
79
|
+
3. Commit your changes (`git commit -am 'Add some new feature.'`).
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
81
|
+
5. Create a new pull request.
|
82
|
+
|
83
|
+
## License
|
84
|
+
See [LICENSE](https://github.com/majjoha/antidote/blob/master/LICENSE).
|
85
|
+
Copyright (c) 2014 Mathias Jean Johansen <<mathias@mjj.io>>
|
data/lib/antidote.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative "antidote/variable_type_error"
|
2
|
+
require_relative "antidote/return_type_error"
|
3
|
+
|
4
|
+
module Antidote
|
5
|
+
class << self
|
6
|
+
def included(klass)
|
7
|
+
klass.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
@@type_constraints = Hash.new { |h, k| h[k] = [] }
|
13
|
+
|
14
|
+
def annotate(*variables, return_type, &method_name)
|
15
|
+
method_name = method_name.call.to_s
|
16
|
+
add_type_constraints(method_name, variables, return_type)
|
17
|
+
redefine_instance_method(method_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def annotate_class_method(*variables, return_type, &method_name)
|
21
|
+
method_name = "self.#{method_name.call.to_s}"
|
22
|
+
add_type_constraints(method_name, variables, return_type)
|
23
|
+
redefine_class_method(method_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add_type_constraints(method_name, variables, return_type)
|
29
|
+
variables.each { |variable| @@type_constraints[method_name] << variable }
|
30
|
+
@@type_constraints[method_name] << {__returns: return_type}
|
31
|
+
end
|
32
|
+
|
33
|
+
def redefine_class_method(method_name)
|
34
|
+
method_name = method_name.split(".").last
|
35
|
+
new_name_for_old_method = "#{method_name}_old".to_sym
|
36
|
+
self.singleton_class.send(:alias_method, new_name_for_old_method, method_name)
|
37
|
+
|
38
|
+
define_singleton_method(method_name) do |*args|
|
39
|
+
args.each_with_index do |argument, index|
|
40
|
+
class_method = "self.#{method_name}"
|
41
|
+
actual = argument.class
|
42
|
+
expected = @@type_constraints[class_method][index]
|
43
|
+
parameters = self.method(new_name_for_old_method).parameters.map(&:last)
|
44
|
+
variable_name = parameters[index]
|
45
|
+
@__return_type = @@type_constraints[class_method].last[:__returns]
|
46
|
+
|
47
|
+
unless actual == expected
|
48
|
+
raise VariableTypeError, [actual, expected, variable_name, class_method]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if (klass = self.send(new_name_for_old_method, *args).class) != @__return_type
|
53
|
+
raise ReturnTypeError, [klass, @__return_type, class_method]
|
54
|
+
else
|
55
|
+
self.send(new_name_for_old_method, *args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def redefine_instance_method(method_name)
|
61
|
+
new_name_for_old_method = "#{method_name}_old".to_sym
|
62
|
+
alias_method(new_name_for_old_method, method_name)
|
63
|
+
|
64
|
+
define_method(method_name) do |*args|
|
65
|
+
args.each_with_index do |argument, index|
|
66
|
+
actual = argument.class
|
67
|
+
expected = @@type_constraints[method_name][index]
|
68
|
+
parameters = self.method(new_name_for_old_method).parameters.map(&:last)
|
69
|
+
variable_name = parameters[index]
|
70
|
+
@__return_type = @@type_constraints[method_name].last[:__returns]
|
71
|
+
|
72
|
+
unless actual == expected
|
73
|
+
raise VariableTypeError, [actual, expected, variable_name, method_name]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if (klass = self.send(new_name_for_old_method, *args).class) != @__return_type
|
78
|
+
raise ReturnTypeError, [klass, @__return_type, method_name]
|
79
|
+
else
|
80
|
+
self.send(new_name_for_old_method, *args)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Antidote
|
2
|
+
class ReturnTypeError < StandardError
|
3
|
+
def initialize(types)
|
4
|
+
@actual_type, @expected_type, @method_name = types
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
"Expected `#{@method_name}` to return a #{@expected_type}, but it " \
|
9
|
+
"returned a #{@actual_type} instead"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Antidote
|
2
|
+
class VariableTypeError < StandardError
|
3
|
+
def initialize(types)
|
4
|
+
@actual_type, @expected_type, @variable_name, @method_name = types
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
"Variable `#{@variable_name}` in method `#{@method_name}` expected a " \
|
9
|
+
"#{@expected_type}, but received a #{@actual_type}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Antidote do
|
4
|
+
describe Foo do
|
5
|
+
let(:foo) { Foo.new }
|
6
|
+
|
7
|
+
describe "#bar" do
|
8
|
+
it "raises an error when the method is given the wrong type" do
|
9
|
+
expect { foo.bar(5.4) }.to raise_error(Antidote::VariableTypeError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns x when given the right argument" do
|
13
|
+
expect(foo.bar(5)).to eq 5
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#baz" do
|
18
|
+
let(:custom_type) { CustomType.new(1, 2) }
|
19
|
+
|
20
|
+
it "raises an error when the method is given the wrong types" do
|
21
|
+
expect { foo.baz(5.5) }.to raise_error(Antidote::VariableTypeError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns x.a + x.b when given the right arguments" do
|
25
|
+
expect(foo.baz(custom_type)).to eq 3
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#qux" do
|
30
|
+
it "raises an error when the return type is wrong" do
|
31
|
+
expect { foo.qux(1,2) }.to raise_error(Antidote::ReturnTypeError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ".quux" do
|
36
|
+
it "raises an error when the method is given the wrong types" do
|
37
|
+
expect { Foo.quux(43, 6.66) }.to raise_error(Antidote::VariableTypeError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns [name, y] when given the right arguments" do
|
41
|
+
expect(Foo.quux(6.66, "Hello")).to eq ["Hello", 6.66]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'rspec'
|
3
|
+
require './lib/antidote'
|
4
|
+
|
5
|
+
class CustomType
|
6
|
+
attr_accessor :a, :b
|
7
|
+
|
8
|
+
def initialize(a, b)
|
9
|
+
@a = a
|
10
|
+
@b = b
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Foo
|
15
|
+
include Antidote
|
16
|
+
|
17
|
+
annotate Fixnum, Fixnum do
|
18
|
+
def bar(x)
|
19
|
+
x
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
annotate CustomType, Fixnum do
|
24
|
+
def baz(z)
|
25
|
+
z.a + z.b
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
annotate Fixnum, Fixnum, String do
|
30
|
+
def qux(a, b)
|
31
|
+
a + b
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
annotate_class_method Float, String, Array do
|
36
|
+
def self.quux(y, name)
|
37
|
+
[name, y]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: antidote-types
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mathias Jean Johansen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 10.3.1
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 10.3.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coveralls
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.7'
|
55
|
+
description: |-
|
56
|
+
Antidote is a small, highly experimental library that
|
57
|
+
performs runtime type assertions in Ruby
|
58
|
+
email: mathias@mjj.io
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- LICENSE
|
64
|
+
- README.md
|
65
|
+
- lib/antidote.rb
|
66
|
+
- lib/antidote/return_type_error.rb
|
67
|
+
- lib/antidote/variable_type_error.rb
|
68
|
+
- lib/antidote/version.rb
|
69
|
+
- spec/antidote_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
homepage: https://github.com/majjoha/antidote
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.2.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: A highly experimental type assertion library
|
95
|
+
test_files:
|
96
|
+
- spec/antidote_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
has_rdoc:
|