diff --git a/lib/contracts.rb b/lib/contracts.rb
index b71b45c..2e6e9ea 100644
--- a/lib/contracts.rb
+++ b/lib/contracts.rb
@@ -108,7 +108,7 @@ def pretty_contract c
def to_s
args = args_contracts.map { |c| pretty_contract(c) }.join(", ")
ret = pretty_contract(ret_contract)
- ("#{args} => #{ret}").gsub("Contracts::", "")
+ ("#{args} => #{ret}").gsub("Contracts::Builtin::", "")
end
# Given a hash, prints out a failure message.
diff --git a/lib/contracts/builtin_contracts.rb b/lib/contracts/builtin_contracts.rb
index 2ef4158..1430d6a 100644
--- a/lib/contracts/builtin_contracts.rb
+++ b/lib/contracts/builtin_contracts.rb
@@ -19,460 +19,465 @@
# 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
- end
-
- # Check that an argument is a positive number.
- class Pos
- def self.valid? val
- val && val.is_a?(Numeric) && val > 0
- end
- end
-
- # Check that an argument is a negative number.
- class Neg
- def self.valid? val
- val && val.is_a?(Numeric) && val < 0
+ module Builtin
+ # Check that an argument is +Numeric+.
+ class Num
+ def self.valid? val
+ val.is_a? Numeric
+ end
end
- end
- # Check that an argument is a natural number.
- class Nat
- def self.valid? val
- val && val.is_a?(Integer) && val >= 0
+ # Check that an argument is a positive number.
+ class Pos
+ def self.valid? val
+ val && val.is_a?(Numeric) && val > 0
+ end
end
- end
- # Passes for any argument.
- class Any
- def self.valid? val
- true
+ # Check that an argument is a negative number.
+ class Neg
+ def self.valid? val
+ val && val.is_a?(Numeric) && val < 0
+ end
end
- end
- # Fails for any argument.
- class None
- def self.valid? val
- false
+ # Check that an argument is a natural number.
+ class Nat
+ def self.valid? val
+ val && val.is_a?(Integer) && val >= 0
+ end
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
+ # Passes for any argument.
+ class Any
+ def self.valid? val
+ true
+ end
end
- def valid?(val)
- @vals.any? do |contract|
- res, _ = Contract.valid?(val, contract)
- res
+ # Fails for any argument.
+ class None
+ def self.valid? val
+ false
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
+ # 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
- 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
+ # 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)
- results = @vals.map do |contract|
- res, _ = Contract.valid?(val, contract)
- res
+ def valid?(val)
+ @vals.any? do |contract|
+ res, _ = Contract.valid?(val, contract)
+ res
+ end
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
+ def to_s
+ @vals[0, @vals.size-1].map do |x|
+ InspectWrapper.create(x)
+ end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
+ end
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
+ # 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)
- @vals.all? do |contract|
- res, _ = Contract.valid?(val, contract)
- res
+ def valid?(val)
+ results = @vals.map do |contract|
+ res, _ = Contract.valid?(val, contract)
+ res
+ end
+ results.count(true) == 1
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
+ def to_s
+ @vals[0, @vals.size-1].map do |x|
+ InspectWrapper.create(x)
+ end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
+ end
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
+ # 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)
- @meths.all? do |meth|
- val.respond_to? meth
+ def valid?(val)
+ @vals.all? do |contract|
+ res, _ = Contract.valid?(val, contract)
+ res
+ end
end
- end
- def to_s
- "a value that responds to #{@meths.inspect}"
+ 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
- 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
+ # 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.send(meth)
+ def valid?(val)
+ @meths.all? do |meth|
+ val.respond_to? meth
+ end
end
- end
- def to_s
- "a value that returns true for all of #{@meths.inspect}"
+ def to_s
+ "a value that responds to #{@meths.inspect}"
+ end
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
+ # 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)
- val.class == @cls
- end
+ def valid?(val)
+ @meths.all? do |meth|
+ val.send(meth)
+ end
+ end
- def to_s
- "exactly #{@cls.inspect}"
+ def to_s
+ "a value that returns true for all of #{@meths.inspect}"
+ end
end
- end
- # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
- # the list, the contract passes.
- #
- # Example: Enum[:a, :b, :c]?
- class Enum < CallableClass
- def initialize(*vals)
- @vals = vals
- 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)
- @vals.include? val
- end
- end
+ def valid?(val)
+ val.class == @cls
+ 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
+ def to_s
+ "exactly #{@cls.inspect}"
+ end
end
- def valid?(val)
- @value.equal?(val)
- end
+ # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
+ # the list, the contract passes.
+ #
+ # Example: Enum[:a, :b, :c]?
+ class Enum < CallableClass
+ def initialize(*vals)
+ @vals = vals
+ end
- def to_s
- "to be equal to #{@value.inspect}"
+ def valid?(val)
+ @vals.include? val
+ end
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
+ # 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)
- @vals.all? do |contract|
- res, _ = Contract.valid?(val, contract)
- !res
+ def valid?(val)
+ @value.equal?(val)
end
- end
- def to_s
- "a value that is none of #{@vals.inspect}"
+ def to_s
+ "to be equal to #{@value.inspect}"
+ end
end
- end
- # @private
- # Takes a collection(responds to :each) type and a contract.
- # The related argument must be of specified collection type.
- # Checks the contract against every element of the collection.
- # If it passes for all elements, the contract passes.
- # Example: CollectionOf[Array, Num]
- class CollectionOf < CallableClass
- def initialize(collection_class, contract)
- @collection_class = collection_class
- @contract = contract
- 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?(vals)
- return false unless vals.is_a?(@collection_class)
- vals.all? do |val|
- res, _ = Contract.valid?(val, @contract)
- res
+ def valid?(val)
+ @vals.all? do |contract|
+ res, _ = Contract.valid?(val, contract)
+ !res
+ end
end
- end
- def to_s
- "a collection #{@collection_class} of #{@contract}"
+ def to_s
+ "a value that is none of #{@vals.inspect}"
+ end
end
- class Factory
- def initialize(collection_class, &before_new)
+ # @private
+ # Takes a collection(responds to :each) type and a contract.
+ # The related argument must be of specified collection type.
+ # Checks the contract against every element of the collection.
+ # If it passes for all elements, the contract passes.
+ # Example: CollectionOf[Array, Num]
+ class CollectionOf < CallableClass
+ def initialize(collection_class, contract)
@collection_class = collection_class
- @before_new = before_new
+ @contract = contract
end
- def new(contract)
- @before_new && @before_new.call
- CollectionOf.new(@collection_class, contract)
+ def valid?(vals)
+ return false unless vals.is_a?(@collection_class)
+ vals.all? do |val|
+ res, _ = Contract.valid?(val, @contract)
+ res
+ end
end
- alias_method :[], :new
- end
- end
+ def to_s
+ "a collection #{@collection_class} of #{@contract}"
+ 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]
- ArrayOf = CollectionOf::Factory.new(Array)
-
- # Takes a contract. The related argument must be a set.
- # Checks the contract against every element of the set.
- # If it passes for all elements, the contract passes.
- # Example: SetOf[Num]
- SetOf = CollectionOf::Factory.new(Set)
-
- # 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
+ class Factory
+ def initialize(collection_class, &before_new)
+ @collection_class = collection_class
+ @before_new = before_new
+ end
- def to_s
- "Args[#{@contract}]"
- end
- end
+ def new(contract)
+ @before_new && @before_new.call
+ CollectionOf.new(@collection_class, contract)
+ end
- class Bool
- def self.valid? val
- val.is_a?(TrueClass) || val.is_a?(FalseClass)
+ alias_method :[], :new
+ end
end
- end
- # Use this to specify a Range object of a particular datatype.
- # Example: RangeOf[Nat], RangeOf[Date], ...
- class RangeOf < CallableClass
- def initialize(contract)
- @contract = contract
- 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]
+ ArrayOf = CollectionOf::Factory.new(Array)
+
+ # Takes a contract. The related argument must be a set.
+ # Checks the contract against every element of the set.
+ # If it passes for all elements, the contract passes.
+ # Example: SetOf[Num]
+ SetOf = CollectionOf::Factory.new(Set)
+
+ # 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 valid?(val)
- val.is_a?(Range) &&
- Contract.valid?(val.first, @contract) &&
- Contract.valid?(val.last, @contract)
+ def to_s
+ "Args[#{@contract}]"
+ end
end
- def to_s
- "a range of #{@contract}"
+ class Bool
+ def self.valid? val
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
+ end
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
- INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
-
- def initialize(key, value = nil)
- if value
- @key = key
- @value = value
- else
- validate_hash(key)
- @key = key.keys.first
- @value = key[@key]
+ # Use this to specify a Range object of a particular datatype.
+ # Example: RangeOf[Nat], RangeOf[Date], ...
+ class RangeOf < CallableClass
+ def initialize(contract)
+ @contract = contract
end
- end
- def valid?(hash)
- return false unless hash.is_a?(Hash)
- keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
- vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
+ def valid?(val)
+ val.is_a?(Range) &&
+ Contract.valid?(val.first, @contract) &&
+ Contract.valid?(val.last, @contract)
+ end
- [keys_match, vals_match].all?
+ def to_s
+ "a range of #{@contract}"
+ end
end
- def to_s
- "Hash<#{@key}, #{@value}>"
- 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
+ INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
+
+ def initialize(key, value = nil)
+ if value
+ @key = key
+ @value = value
+ else
+ validate_hash(key)
+ @key = key.keys.first
+ @value = key[@key]
+ end
+ end
- private
+ def valid?(hash)
+ return false unless hash.is_a?(Hash)
+ keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
+ vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
- def validate_hash(hash)
- fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
- end
- end
+ [keys_match, vals_match].all?
+ end
- # Use this for specifying contracts for keyword arguments
- # Example: KeywordArgs[ e: Range, f: Optional[Num] ]
- class KeywordArgs < CallableClass
- def initialize(options)
- @options = options
- end
+ def to_s
+ "Hash<#{@key}, #{@value}>"
+ end
+
+ private
- def valid?(hash)
- return false unless hash.keys - options.keys == []
- options.all? do |key, contract|
- Optional._valid?(hash, key, contract)
+ def validate_hash(hash)
+ fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
end
end
- def to_s
- "KeywordArgs[#{options}]"
- end
+ # Use this for specifying contracts for keyword arguments
+ # Example: KeywordArgs[ e: Range, f: Optional[Num] ]
+ class KeywordArgs < CallableClass
+ def initialize(options)
+ @options = options
+ end
- def inspect
- to_s
- end
+ def valid?(hash)
+ return false unless hash.keys - options.keys == []
+ options.all? do |key, contract|
+ Optional._valid?(hash, key, contract)
+ end
+ end
- private
+ def to_s
+ "KeywordArgs[#{options}]"
+ end
- attr_reader :options
- end
+ def inspect
+ to_s
+ end
- # Use this for specifying optional keyword argument
- # Example: Optional[Num]
- class Optional < CallableClass
- UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
- "Unable to use Optional contract outside of KeywordArgs contract"
+ private
- def self._valid?(hash, key, contract)
- return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
- contract.within_opt_hash!
- !hash.key?(key) || Contract.valid?(hash[key], contract)
+ attr_reader :options
end
- def initialize(contract)
- @contract = contract
- @within_opt_hash = false
- end
+ # Use this for specifying optional keyword argument
+ # Example: Optional[Num]
+ class Optional < CallableClass
+ UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
+ "Unable to use Optional contract outside of KeywordArgs contract"
- def within_opt_hash!
- @within_opt_hash = true
- self
- end
+ def self._valid?(hash, key, contract)
+ return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
+ contract.within_opt_hash!
+ !hash.key?(key) || Contract.valid?(hash[key], contract)
+ end
- def valid?(value)
- ensure_within_opt_hash
- Contract.valid?(value, contract)
- end
+ def initialize(contract)
+ @contract = contract
+ @within_opt_hash = false
+ end
- def to_s
- "Optional[#{formatted_contract}]"
- end
+ def within_opt_hash!
+ @within_opt_hash = true
+ self
+ end
- def inspect
- to_s
- end
+ def valid?(value)
+ ensure_within_opt_hash
+ Contract.valid?(value, contract)
+ end
- private
+ def to_s
+ "Optional[#{formatted_contract}]"
+ end
- attr_reader :contract, :within_opt_hash
+ def inspect
+ to_s
+ end
- def ensure_within_opt_hash
- return if within_opt_hash
- fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
- end
+ private
- def formatted_contract
- Formatters::InspectWrapper.create(contract)
- end
- end
+ attr_reader :contract, :within_opt_hash
+
+ def ensure_within_opt_hash
+ return if within_opt_hash
+ fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
+ 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]))
+ def formatted_contract
+ Formatters::InspectWrapper.create(contract)
+ end
end
- def include_proc?
- @vals.include? Proc
+ # 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
+
+ def include_proc?
+ @vals.include? Proc
+ end
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
+ # 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
+
+ # Users can still include `Contracts::Core` & `Contracts::Builtin`
+ include Builtin
end
diff --git a/lib/contracts/formatters.rb b/lib/contracts/formatters.rb
index d97d78f..db9b107 100644
--- a/lib/contracts/formatters.rb
+++ b/lib/contracts/formatters.rb
@@ -90,7 +90,7 @@ def full?
def plain?
# Not a type of contract that can have a custom to_s defined
- !@value.is_a?(CallableClass) && @value.class != Class
+ !@value.is_a?(Builtin::CallableClass) && @value.class != Class
end
def useful_to_s?
@@ -103,7 +103,7 @@ def empty_to_s?
end
def strip_prefix(val)
- val.gsub(/^Contracts::/, "")
+ val.gsub(/^Contracts::Builtin::/, "")
end
end
diff --git a/spec/builtin_contracts_spec.rb b/spec/builtin_contracts_spec.rb
index 2f980e6..7db5bbf 100644
--- a/spec/builtin_contracts_spec.rb
+++ b/spec/builtin_contracts_spec.rb
@@ -392,7 +392,7 @@ def something(hash)
end
context "given String => Num" do
- it { expect(Contracts::HashOf[String, Contracts::Num].to_s).to eq("Hash") }
+ it { expect(Contracts::HashOf[String, Contracts::Num].to_s).to eq("Hash") }
end
end
end