contracts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+