contracts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,169 @@
1
+ module Contracts
2
+ class Num
3
+ def self.valid? val
4
+ val.is_a? Numeric
5
+ end
6
+ end
7
+
8
+ class Pos
9
+ def self.valid? val
10
+ val > 0
11
+ end
12
+ end
13
+
14
+ class Neg
15
+ def self.valid? val
16
+ val < 0
17
+ end
18
+ end
19
+
20
+ class Any
21
+ def self.valid? val
22
+ true
23
+ end
24
+ end
25
+
26
+ class None
27
+ def self.valid? val
28
+ false
29
+ end
30
+ end
31
+
32
+ class CallableClass
33
+ def self.[](*vals)
34
+ self.new(*vals)
35
+ end
36
+ end
37
+
38
+ class Or < CallableClass
39
+ def initialize(*vals)
40
+ @vals = vals
41
+ end
42
+
43
+ def valid?(val)
44
+ @vals.any? do |contract|
45
+ res, _ = Contract.valid?(val, contract)
46
+ res
47
+ end
48
+ end
49
+
50
+ def to_s
51
+ @vals[0, @vals.size-1].join(", ") + " or " + @vals[-1].to_s
52
+ end
53
+ end
54
+
55
+ class Xor < CallableClass
56
+ def initialize(*vals)
57
+ @vals = vals
58
+ end
59
+
60
+ def valid?(val)
61
+ results = @vals.map do |contract|
62
+ res, _ = Contract.valid?(val, contract)
63
+ res
64
+ end
65
+ results.count(true) == 1
66
+ end
67
+
68
+ def to_s
69
+ @vals[0, @vals.size-1].join(", ") + " xor " + @vals[-1].to_s
70
+ end
71
+ end
72
+
73
+ class And < CallableClass
74
+ def initialize(*vals)
75
+ @vals = vals
76
+ end
77
+
78
+ def valid?(val)
79
+ @vals.all? do |contract|
80
+ res, _ = Contract.valid?(val, contract)
81
+ res
82
+ end
83
+ end
84
+
85
+ def to_s
86
+ @vals[0, @vals.size-1].join(", ") + " and " + @vals[-1].to_s
87
+ end
88
+ end
89
+
90
+ class RespondsTo < CallableClass
91
+ def initialize(*meths)
92
+ @meths = meths
93
+ end
94
+
95
+ def valid?(val)
96
+ @meths.all? do |meth|
97
+ val.respond_to? meth
98
+ end
99
+ end
100
+
101
+ def to_s
102
+ "a value that responds to #{@meths.inspect}"
103
+ end
104
+ end
105
+
106
+ class Send < CallableClass
107
+ def initialize(*meths)
108
+ @meths = meths
109
+ end
110
+
111
+ def valid?(val)
112
+ @meths.all? do |meth|
113
+ val.send(meth)
114
+ end
115
+ end
116
+
117
+ def to_s
118
+ "a value that returns true for all of #{@meths.inspect}"
119
+ end
120
+ end
121
+
122
+ class IsA < CallableClass
123
+ def initialize(cls)
124
+ @cls = cls
125
+ end
126
+
127
+ def valid?(val)
128
+ val.is_a? @cls
129
+ end
130
+
131
+ def to_s
132
+ "a #{@cls.inspect}"
133
+ end
134
+ end
135
+
136
+ class Not < CallableClass
137
+ def initialize(*vals)
138
+ @vals = vals
139
+ end
140
+
141
+ def valid?(val)
142
+ @vals.all? do |contract|
143
+ res, _ = Contract.valid?(val, contract)
144
+ !res
145
+ end
146
+ end
147
+
148
+ def to_s
149
+ "a value that is none of #{@vals.inspect}"
150
+ end
151
+ end
152
+
153
+ class ArrayOf < CallableClass
154
+ def initialize(contract)
155
+ @contract = contract
156
+ end
157
+
158
+ def valid?(vals)
159
+ vals.all? do |val|
160
+ res, _ = Contract.valid?(val, @contract)
161
+ res
162
+ end
163
+ end
164
+
165
+ def to_s
166
+ "an array of #{@contract}"
167
+ end
168
+ end
169
+ end
data/lib/contracts.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'decorators'
2
+ require 'builtin_contracts'
3
+
4
+ class Class
5
+ include MethodDecorators
6
+ end
7
+
8
+ class Contract < Decorator
9
+ attr_accessor :contracts, :klass, :method
10
+ decorator_name :contract
11
+ def initialize(klass, method, *contracts)
12
+ @klass, @method, @contracts = klass, method, contracts
13
+ end
14
+
15
+ def self.mkerror(validates, arg, contract)
16
+ if validates
17
+ [true, {}]
18
+ else
19
+ [false, { :arg => arg, :contract => contract }]
20
+ end
21
+ end
22
+
23
+ def self.failure_msg(data)
24
+ # TODO __file__ and __line__ won't work in Ruby 1.9.
25
+ # It provides a source_location method instead.
26
+ %{Contract violation:
27
+ Expected: #{data[:contract]},
28
+ Actual: #{data[:arg].inspect}
29
+ Value guarded in: #{data[:class]}::#{data[:method].name}
30
+ With Contract: #{data[:contracts].map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(", ") }
31
+ At: #{data[:method].__file__}:#{data[:method].__line__} }
32
+ end
33
+ def self.failure_callback(data)
34
+ raise failure_msg(data)
35
+ end
36
+
37
+ def self.success_callback(data)
38
+ end
39
+
40
+ def self.validate_hash(arg, contract)
41
+ arg.keys.each do |k|
42
+ result, info = validate(arg[k], contract[k])
43
+ return [result, info] unless result
44
+ end
45
+ end
46
+
47
+ def self.validate_proc(arg, contract)
48
+ mkerror(contract[arg], arg, contract)
49
+ end
50
+
51
+ def self.validate_class(arg, contract)
52
+ valid = if contract.respond_to? :valid?
53
+ contract.valid? arg
54
+ else
55
+ contract == arg.class
56
+ end
57
+ mkerror(valid, arg, contract)
58
+ end
59
+
60
+ def self.validate_all(args, contracts, klass, method)
61
+ args.zip(contracts).each do |arg, contract|
62
+ validate(arg, contract, klass, method, contracts)
63
+ end
64
+ end
65
+
66
+ def self.validate(arg, contract, klass, method, contracts)
67
+ result, _ = valid?(arg, contract)
68
+ if result
69
+ success_callback({:arg => arg, :contract => contract, :class => klass, :method => method, :contracts => contracts})
70
+ else
71
+ failure_callback({:arg => arg, :contract => contract, :class => klass, :method => method, :contracts => contracts})
72
+ end
73
+ end
74
+
75
+ # arg to method -> contract it should satisfy -> (Boolean, metadata)
76
+ def self.valid?(arg, contract)
77
+ case contract
78
+ when Class
79
+ validate_class arg, contract
80
+ when Proc
81
+ validate_proc arg, contract
82
+ when Array
83
+ # TODO account for these errors too
84
+ return mkerror(false, arg, contract) unless arg.is_a?(Array)
85
+ validate_all(arg, contract)
86
+ when Hash
87
+ return mkerror(false, arg, contract) unless arg.is_a?(Hash)
88
+ validate_hash(arg, contract)
89
+ else
90
+ if contract.respond_to? :valid?
91
+ mkerror(contract.valid?(arg), arg, contract)
92
+ else
93
+ mkerror(arg == contract, arg, contract)
94
+ end
95
+ end
96
+ end
97
+
98
+ def call(this, *args)
99
+ Contract.validate_all(args, @contracts, @klass, @method)
100
+ result = @method.bind(this).call(*args)
101
+ if args.size == @contracts.size - 1
102
+ Contract.validate(result, @contracts[-1], @klass, @method, @contracts)
103
+ end
104
+ result
105
+ end
106
+ end
data/lib/decorators.rb ADDED
@@ -0,0 +1,84 @@
1
+ module MethodDecorators
2
+ def self.extended(klass)
3
+ class << klass
4
+ attr_accessor :decorated_methods
5
+ end
6
+ end
7
+
8
+ def method_missing(name, *args, &blk)
9
+ # if the const isn't capitalized, then const_defined will throw an
10
+ # error. So first check to make sure it is capitalized.
11
+ letter = name.to_s[0]
12
+ if letter >= 65 && letter <= 90 && Object.const_defined?(name)
13
+ const = Object.const_get(name)
14
+ elsif Decorator.decorators.key?(name)
15
+ const = Decorator.decorators[name]
16
+ else
17
+ return super
18
+ end
19
+
20
+ instance_eval <<-ruby_eval, __FILE__, __LINE__ + 1
21
+ def #{name}(*args, &blk)
22
+ decorate(#{const.name}, *args, &blk)
23
+ end
24
+ ruby_eval
25
+
26
+ send(name, *args, &blk)
27
+ end
28
+
29
+ def method_added(name)
30
+ return unless @decorators
31
+
32
+ decorators = @decorators.dup
33
+ @decorators = nil
34
+ @decorated_methods ||= Hash.new {|h,k| h[k] = []}
35
+
36
+ class << self; attr_accessor :decorated_methods; end
37
+
38
+ decorators.each do |klass, args|
39
+ decorator = klass.respond_to?(:new) ? klass.new(self, instance_method(name), *args) : klass
40
+ @decorated_methods[name] << decorator
41
+ end
42
+
43
+ class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
44
+ def #{name}(*args, &blk)
45
+ ret = nil
46
+ self.class.decorated_methods[#{name.inspect}].each do |decorator|
47
+ ret = decorator.call(self, *args, &blk)
48
+ end
49
+ ret
50
+ end
51
+ ruby_eval
52
+ end
53
+
54
+ def decorate(klass, *args)
55
+ @decorators ||= []
56
+ @decorators << [klass, args]
57
+ end
58
+ end
59
+
60
+ class Decorator
61
+ class << self
62
+ attr_accessor :decorators
63
+ def decorator_name(name)
64
+ Decorator.decorators ||= {}
65
+ Decorator.decorators[name] = self
66
+ end
67
+ end
68
+
69
+ def self.inherited(klass)
70
+ name = klass.name.gsub(/^./) {|m| m.downcase}
71
+
72
+ return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
73
+
74
+ MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
75
+ def #{klass}(*args, &blk)
76
+ decorate(#{klass}, *args, &blk)
77
+ end
78
+ ruby_eval
79
+ end
80
+
81
+ def initialize(klass, method)
82
+ @method = method
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contracts
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Aditya Bhargava
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-07 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: "This library provides contracts for Ruby. Contracts let you clearly \xE2\x80\x93 even beautifully \xE2\x80\x93 express how your code behaves, and free you from writing tons of boilerplate, defensive code."
23
+ email: bluemangroupie@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/builtin_contracts.rb
32
+ - lib/contracts.rb
33
+ - lib/decorators.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/egonSchiele/contracts.ruby
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ hash: 3
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.7
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Contracts for Ruby.
68
+ test_files: []
69
+