1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
# frozen_string_literal: true
require "pp"
module Contracts
# A namespace for classes related to formatting.
module Formatters
# Used to format contracts for the `Expected:` field of error output.
class Expected
# @param full [Boolean] if false only unique `to_s` values will be output,
# non unique values become empty string.
def initialize(contract, full: true)
@contract, @full = contract, full
end
# Formats any type of Contract.
def contract(contract = @contract)
case contract
when Hash
hash_contract(contract)
when Array
array_contract(contract)
else
InspectWrapper.create(contract, full: @full)
end
end
# Formats Hash contracts.
def hash_contract(hash)
@full = true # Complex values output completely, overriding @full
hash.inject({}) do |repr, (k, v)|
repr.merge(k => InspectWrapper.create(contract(v), full: @full))
end
end
# Formats Array contracts.
def array_contract(array)
@full = true
array.map { |v| InspectWrapper.create(contract(v), full: @full) }
end
end
# A wrapper class to produce correct inspect behaviour for different
# contract values - constants, Class contracts, instance contracts etc.
module InspectWrapper
# InspectWrapper is a factory, will never be an instance
# @return [ClassInspectWrapper, ObjectInspectWrapper]
def self.create(value, full: true)
if value.instance_of?(Class)
ClassInspectWrapper
else
ObjectInspectWrapper
end.new(value, full)
end
# @param full [Boolean] if false only unique `to_s` values will be output,
# non unique values become empty string.
def initialize(value, full)
@value, @full = value, full
end
# Inspect different types of contract values.
# Contracts module prefix will be removed from classes.
# Custom to_s messages will be wrapped in round brackets to differentiate
# from standard Strings.
# Primitive values e.g. 42, true, nil will be left alone.
def inspect
return "" unless full?
return @value.inspect if empty_val?
return @value.to_s if plain?
return delim(@value.to_s) if useful_to_s?
useful_inspect
end
def delim(value)
@full ? "(#{value})" : "#{value}"
end
# Eliminates eronious quotes in output that plain inspect includes.
def to_s
inspect
end
private
def empty_val?
@value.nil? || @value == ""
end
def full?
@full ||
@value.is_a?(Hash) || @value.is_a?(Array) ||
(!plain? && useful_to_s?)
end
def plain?
# Not a type of contract that can have a custom to_s defined
!@value.is_a?(Builtin::CallableClass) && @value.class != Class
end
def useful_to_s?
# Useless to_s value or no custom to_s behaviour defined
!empty_to_s? && custom_to_s?
end
def empty_to_s?
@value.to_s.empty?
end
def strip_prefix(val)
val.gsub(/^Contracts::Builtin::/, "")
end
end
class ClassInspectWrapper
include InspectWrapper
def custom_to_s?
@value.to_s != @value.name
end
def useful_inspect
strip_prefix(empty_to_s? ? @value.name : @value.inspect)
end
end
class ObjectInspectWrapper
include InspectWrapper
def custom_to_s?
!@value.to_s.match(/#<\w+:.+>/)
end
def useful_inspect
strip_prefix(empty_to_s? ? @value.class.name : @value.inspect)
end
end
end
end
|