File: conversions.rb

package info (click to toggle)
ruby-naught 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 180 kB
  • sloc: ruby: 658; makefile: 6
file content (129 lines) | stat: -rw-r--r-- 4,213 bytes parent folder | download
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
module Naught
  # Helper conversion API available on generated null classes
  #
  # This module is designed to be configured per null class via
  # {Conversions.configure}. Each generated null class gets its
  # own configured version of these conversion functions.
  #
  # @api public
  module Conversions
    # Sentinel value for no argument passed
    NOTHING_PASSED = Object.new.freeze
    private_constant :NOTHING_PASSED

    class << self
      # Configure a Conversions module for a specific null class
      #
      # @param mod [Module] module to configure
      # @param null_class [Class] the generated null class
      # @param null_equivs [Array] values treated as null-equivalent
      # @return [void]
      # @api private
      def configure(mod, null_class:, null_equivs:)
        mod.define_method(:__null_class__) { null_class }
        mod.define_method(:__null_equivs__) { null_equivs }
        mod.send(:private, :__null_class__, :__null_equivs__)
      end
    end

    # Return a null object for +object+ if it is null-equivalent
    #
    # @example
    #   include MyNullObject::Conversions
    #   Null()       #=> <null>
    #   Null(nil)    #=> <null>
    #
    # @param object [Object] candidate object
    # @return [Object] a null object
    # @raise [ArgumentError] if +object+ is not null-equivalent
    def Null(object = NOTHING_PASSED)
      return object if null_object?(object)
      return make_null(1) if null_equivalent?(object, include_nothing: true)

      raise ArgumentError, "Null() requires a null-equivalent value, " \
                           "got #{object.class}: #{object.inspect}"
    end

    # Return a null object for null-equivalent values, otherwise the value
    #
    # @example
    #   Maybe(nil)        #=> <null>
    #   Maybe("hello")    #=> "hello"
    #
    # @param object [Object] candidate object
    # @yieldreturn [Object] optional lazy value
    # @return [Object] null object or original value
    def Maybe(object = nil)
      object = yield if block_given?
      return object if null_object?(object)
      return make_null(1) if null_equivalent?(object)

      object
    end

    # Return the value if not null-equivalent, otherwise raise
    #
    # @example
    #   Just("hello")  #=> "hello"
    #   Just(nil)      # raises ArgumentError
    #
    # @param object [Object] candidate object
    # @yieldreturn [Object] optional lazy value
    # @return [Object] original value
    # @raise [ArgumentError] if value is null-equivalent
    def Just(object = nil)
      object = yield if block_given?
      if null_object?(object) || null_equivalent?(object)
        raise ArgumentError, "Just() requires a non-null value, got: #{object.inspect}"
      end

      object
    end

    # Return +nil+ for null objects, otherwise return the value
    #
    # @example
    #   Actual(null)     #=> nil
    #   Actual("hello")  #=> "hello"
    #
    # @param object [Object] candidate object
    # @yieldreturn [Object] optional lazy value
    # @return [Object, nil] actual value or nil
    def Actual(object = nil)
      object = yield if block_given?
      null_object?(object) ? nil : object
    end

    private

    # Check if an object is a null object
    #
    # @param object [Object] the object to check
    # @return [Boolean] true if the object is a null object
    # @api private
    def null_object?(object)
      NullObjectTag === object
    end

    # Check if an object is null-equivalent (nil or custom null equivalents)
    #
    # @param object [Object] the object to check
    # @param include_nothing [Boolean] whether to treat NOTHING_PASSED as null-equivalent
    # @return [Boolean] true if the object is null-equivalent
    # @api private
    def null_equivalent?(object, include_nothing: false)
      return true if include_nothing && object == NOTHING_PASSED

      __null_equivs__.any? { |equiv| equiv === object }
    end

    # Create a new null object instance
    #
    # @param caller_offset [Integer] additional stack frames to skip
    # @return [Object] a new null object
    # @api private
    def make_null(caller_offset)
      __null_class__.get(caller: caller(caller_offset + 1))
    end
  end
end