File: formatters.rb

package info (click to toggle)
ruby-contracts 0.17-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 624 kB
  • sloc: ruby: 3,805; makefile: 4; sh: 2
file content (140 lines) | stat: -rw-r--r-- 3,774 bytes parent folder | download | duplicates (2)
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