require "contracts/testable"
require "contracts/formatters"
# rdoc
# This module contains all the builtin contracts.
# If you want to use them, first:
#
# import Contracts
#
# And then use these or write your own!
#
# A simple example:
#
# Contract Num, Num => Num
# def add(a, b)
# a + b
# end
#
# The contract is Contract Num, Num, Num.
# That says that the +add+ function takes two numbers and returns a number.
module Contracts
# Check that an argument is +Numeric+.
class Num
def self.valid? val
val.is_a? Numeric
end
def self.testable?
true
end
def self.test_data
[-1, 0, 1, 1.5, 50_000]
end
end
# Check that an argument is a positive number.
class Pos
def self.valid? val
val > 0
end
def testable?
true
end
def self.test_data
(0..5).map { rand(999) + 1 }
end
end
# Check that an argument is a negative number.
class Neg
def self.valid? val
val < 0
end
def testable?
true
end
def self.test_data
(0..5).map { (rand(999) + 1) * -1 }
end
end
# Check that an argument is a natural number.
class Nat
def self.valid? val
val >= 0 && val.integer?
end
def testable?
true
end
def self.test_data
(0..5).map { |n| n * rand(999) }
end
end
# Passes for any argument.
class Any
def self.valid? val
true
end
end
# Fails for any argument.
class None
def self.valid? val
false
end
end
# Use this when you are writing your own contract classes.
# Allows your contract to be called with [] instead of .new:
#
# Old: Or.new(param1, param2)
#
# New: Or[param1, param2]
#
# Of course, .new still works.
class CallableClass
include ::Contracts::Formatters
def self.[](*vals)
new(*vals)
end
end
# Takes a variable number of contracts.
# The contract passes if any of the contracts pass.
# Example: Or[Fixnum, Float]
class Or < CallableClass
def initialize(*vals)
@vals = vals
end
def valid?(val)
@vals.any? do |contract|
res, _ = Contract.valid?(val, contract)
res
end
end
def to_s
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
end
# this can only be tested IF all the sub-contracts have a test_data method
def testable?
@vals.all? do |val|
Testable.testable?(val)
end
end
def test_data
@vals.map do |val|
Testable.test_data(val)
end.flatten
end
end
# Takes a variable number of contracts.
# The contract passes if exactly one of those contracts pass.
# Example: Xor[Fixnum, Float]
class Xor < CallableClass
def initialize(*vals)
@vals = vals
end
def valid?(val)
results = @vals.map do |contract|
res, _ = Contract.valid?(val, contract)
res
end
results.count(true) == 1
end
def to_s
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
end
def testable?
@vals.all? do |val|
Testable.testable? val
end
end
def test_data
@vals.map do |val|
Testable.test_data val
end.flatten
end
end
# Takes a variable number of contracts.
# The contract passes if all contracts pass.
# Example: And[Fixnum, Float]
class And < CallableClass
def initialize(*vals)
@vals = vals
end
def valid?(val)
@vals.all? do |contract|
res, _ = Contract.valid?(val, contract)
res
end
end
def to_s
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
end
end
# Takes a variable number of method names as symbols.
# The contract passes if the argument responds to all
# of those methods.
# Example: RespondTo[:password, :credit_card]
class RespondTo < CallableClass
def initialize(*meths)
@meths = meths
end
def valid?(val)
@meths.all? do |meth|
val.respond_to? meth
end
end
def to_s
"a value that responds to #{@meths.inspect}"
end
end
# Takes a variable number of method names as symbols.
# Given an argument, all of those methods are called
# on the argument one by one. If they all return true,
# the contract passes.
# Example: Send[:valid?]
class Send < CallableClass
def initialize(*meths)
@meths = meths
end
def valid?(val)
@meths.all? do |meth|
val.send(meth)
end
end
def to_s
"a value that returns true for all of #{@meths.inspect}"
end
end
# Takes a class +A+. If argument is object of type +A+, the contract passes.
# If it is a subclass of A (or not related to A in any way), it fails.
# Example: Exactly[Numeric]
class Exactly < CallableClass
def initialize(cls)
@cls = cls
end
def valid?(val)
val.class == @cls
end
def to_s
"exactly #{@cls.inspect}"
end
end
# Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
# otherwise the contract fails.
# Example: Eq[Class]
class Eq < CallableClass
def initialize(value)
@value = value
end
def valid?(val)
@value.equal?(val)
end
def to_s
"to be equal to #{@value.inspect}"
end
end
# Takes a variable number of contracts. The contract
# passes if all of those contracts fail for the given argument.
# Example: Not[nil]
class Not < CallableClass
def initialize(*vals)
@vals = vals
end
def valid?(val)
@vals.all? do |contract|
res, _ = Contract.valid?(val, contract)
!res
end
end
def to_s
"a value that is none of #{@vals.inspect}"
end
end
# Takes a contract. The related argument must be an array.
# Checks the contract against every element of the array.
# If it passes for all elements, the contract passes.
# Example: ArrayOf[Num]
class ArrayOf < CallableClass
def initialize(contract)
@contract = contract
end
def valid?(vals)
return false unless vals.is_a?(Array)
vals.all? do |val|
res, _ = Contract.valid?(val, @contract)
res
end
end
def to_s
"an array of #{@contract}"
end
def testable?
Testable.testable? @contract
end
def test_data
[
[],
[Testable.test_data(@contract)],
[Testable.test_data(@contract), Testable.test_data(@contract)]
]
end
end
# Used for *args (variadic functions). Takes a contract
# and uses it to validate every element passed in
# through *args.
# Example: Args[Or[String, Num]]
class Args < CallableClass
attr_reader :contract
def initialize(contract)
@contract = contract
end
def to_s
"Args[#{@contract}]"
end
def testable?
Testable.testable? @contract
end
def test_data
[
[],
[Testable.test_data(@contract)],
[Testable.test_data(@contract), Testable.test_data(@contract)]
]
end
end
class Bool
def self.valid? val
val.is_a?(TrueClass) || val.is_a?(FalseClass)
end
end
# Use this to specify the Hash characteristics. Takes two contracts,
# one for hash keys and one for hash values.
# Example: HashOf[Symbol, String]
class HashOf < CallableClass
def initialize(key, value)
@key = key
@value = value
end
def valid?(hash)
keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
[keys_match, vals_match].all?
end
def to_s
"Hash<#{@key}, #{@value}>"
end
end
# Takes a Contract.
# The contract passes if the contract passes or the given value is nil.
# Maybe(foo) is equivalent to Or[foo, nil].
class Maybe < Or
def initialize(*vals)
super(*(vals + [nil]))
end
end
# class ::Hash
# def testable?
# values.all? do |val|
# Testable.testable?(val)
# end
# end
# def test_data
# keys = self.keys
# _vals = keys.map do |key|
# ret = Testable.test_data(self[key])
# if ret.is_a? Array
# ret
# else
# [ret]
# end
# end
# all_vals = Testable.product(_vals)
# hashes = []
# all_vals.each do |vals|
# hash = {}
# keys.zip(vals).each do |key, val|
# hash[key] = val
# end
# hashes << hash
# end
# hashes
# end
# end
# class ::String
# def self.testable?
# true
# end
# def self.test_data
# # send a random string
# ("a".."z").to_a.shuffle[0, 10].join
# end
# end
# Used to define contracts on functions passed in as arguments.
# Example: Func[Num => Num] # the function should take a number and return a number
class Func < CallableClass
attr_reader :contracts
def initialize(*contracts)
@contracts = contracts
end
end
end