Skip to content

Instantly share code, notes, and snippets.

@jcinnamond
Created January 17, 2014 15:25
Show Gist options
  • Save jcinnamond/8475230 to your computer and use it in GitHub Desktop.
Save jcinnamond/8475230 to your computer and use it in GitHub Desktop.
module BasicContract
def self.included(base)
contract = Contract.new(base)
contract.require :ping
contract.require :pong => [:name]
contract.require :pung
contract.check!
end
end
class Contract
def initialize(base)
@base = base
@contracts = []
end
def require(name)
contract = Requirement.new(name)
@contracts << contract
contract
end
def debug
puts @contracts.inspect
end
def check!
failures = Broken.new(@base)
@contracts.each do |contract|
contract.check(@base, failures)
end
raise failures if failures.any?
end
class Requirement
def initialize(requirements)
if requirements.is_a?(Hash)
requirements.each_pair do |name, args|
@name = name.to_sym
@args = [args].flatten
end
else
@name = requirements.to_sym
@args = []
end
end
def check(base, failures)
if base.public_instance_methods.include?(@name)
method = base.instance_method(@name)
method_args = method.parameters.select { |p| p[0] == :keyreq }.last || []
failures.missing_parameters(@name, @args - method_args)
failures.extra_parameters(@name, method_args - @args)
else
failures.missing_method(@name)
end
end
end
class Broken < StandardError
def initialize(base)
@base = base
@missing_methods = []
@wrong_signatures = Hash.new { |h, key| h[key] = {missing: [], extra: []} }
end
def missing_method(method_name)
@missing_methods << method_name
end
def missing_parameters(method_name, parameter_names)
@wrong_signatures[method_name] [:missing] = parameter_names unless parameter_names.empty?
end
def extra_parameters(method_name, parameter_names)
@wrong_signatures[method_name][:extra] = parameter_names unless parameter_names.empty?
end
def any?
!(@missing_methods.empty? && @missing_parameters.empty?)
end
def to_s
str = "on #{@base.inspect}:\n"
if !@missing_methods.empty?
str << "\t(#{@missing_methods.count} missing methods)\n"
@missing_methods.each do |method_name|
str << "\t[missing] #{method_name}\n"
end
str << "\n"
end
if !@wrong_signatures.empty?
str << "\t(#{@wrong_signatures.count} methods with the wrong signature)\n"
@wrong_signatures.each do |method_name, details|
err = ""
missing = details[:missing]
err << " missing params: [#{missing.join(', ')}]" unless missing.empty?
extra = details[:extra]
err << " extra required params: [#{extra.join(', ')}]" unless extra.empty?
str << "\t[bad signature] #{method_name} -> #{err}\n"
end
str << "\n"
end
str
end
end
end
$ irb -I. -rcontract -rbasic_contract
irb(main):001:0> require 'test'
Contract::Broken: on Test:
(1 missing methods)
[missing] ping
(2 methods with the wrong signature)
[bad signature] pong -> missing params: [name]
[bad signature] pung -> extra required params: [keyreq, key]
class Test
def pong
end
def pung(key:)
end
include(BasicContract)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment