cuprum 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -0
- data/DEVELOPMENT.md +42 -53
- data/README.md +728 -536
- data/lib/cuprum.rb +12 -6
- data/lib/cuprum/built_in.rb +3 -1
- data/lib/cuprum/built_in/identity_command.rb +6 -4
- data/lib/cuprum/built_in/identity_operation.rb +4 -2
- data/lib/cuprum/built_in/null_command.rb +5 -3
- data/lib/cuprum/built_in/null_operation.rb +4 -2
- data/lib/cuprum/command.rb +37 -59
- data/lib/cuprum/command_factory.rb +50 -24
- data/lib/cuprum/currying.rb +79 -0
- data/lib/cuprum/currying/curried_command.rb +116 -0
- data/lib/cuprum/error.rb +44 -10
- data/lib/cuprum/errors.rb +2 -0
- data/lib/cuprum/errors/command_not_implemented.rb +6 -3
- data/lib/cuprum/errors/operation_not_called.rb +6 -6
- data/lib/cuprum/errors/uncaught_exception.rb +55 -0
- data/lib/cuprum/exception_handling.rb +50 -0
- data/lib/cuprum/matcher.rb +90 -0
- data/lib/cuprum/matcher_list.rb +150 -0
- data/lib/cuprum/matching.rb +232 -0
- data/lib/cuprum/matching/match_clause.rb +65 -0
- data/lib/cuprum/middleware.rb +210 -0
- data/lib/cuprum/operation.rb +17 -15
- data/lib/cuprum/processing.rb +10 -14
- data/lib/cuprum/result.rb +2 -4
- data/lib/cuprum/result_helpers.rb +22 -0
- data/lib/cuprum/rspec/be_a_result.rb +10 -1
- data/lib/cuprum/rspec/be_a_result_matcher.rb +5 -7
- data/lib/cuprum/rspec/be_callable.rb +14 -0
- data/lib/cuprum/steps.rb +233 -0
- data/lib/cuprum/utils.rb +3 -1
- data/lib/cuprum/utils/instance_spy.rb +37 -30
- data/lib/cuprum/version.rb +13 -10
- metadata +34 -19
- data/lib/cuprum/chaining.rb +0 -420
    
        data/lib/cuprum.rb
    CHANGED
    
    | @@ -1,14 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            # A lightweight, functional-lite toolkit for making business logic a first-class
         | 
| 2 4 | 
             
            # citizen of your application.
         | 
| 3 5 | 
             
            module Cuprum
         | 
| 4 | 
            -
              autoload :Command, | 
| 5 | 
            -
              autoload : | 
| 6 | 
            -
              autoload : | 
| 6 | 
            +
              autoload :Command,    'cuprum/command'
         | 
| 7 | 
            +
              autoload :Error,      'cuprum/error'
         | 
| 8 | 
            +
              autoload :Matcher,    'cuprum/matcher'
         | 
| 9 | 
            +
              autoload :Middleware, 'cuprum/middleware'
         | 
| 10 | 
            +
              autoload :Operation,  'cuprum/operation'
         | 
| 11 | 
            +
              autoload :Result,     'cuprum/result'
         | 
| 12 | 
            +
              autoload :Steps,      'cuprum/steps'
         | 
| 7 13 |  | 
| 8 14 | 
             
              class << self
         | 
| 9 15 | 
             
                # @return [String] The current version of the gem.
         | 
| 10 16 | 
             
                def version
         | 
| 11 17 | 
             
                  VERSION
         | 
| 12 | 
            -
                end | 
| 13 | 
            -
              end | 
| 14 | 
            -
            end | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
    
        data/lib/cuprum/built_in.rb
    CHANGED
    
    
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cuprum/built_in'
         | 
| 2 4 | 
             
            require 'cuprum/command'
         | 
| 3 5 |  | 
| @@ -24,8 +26,8 @@ module Cuprum::BuiltIn | |
| 24 26 | 
             
              class IdentityCommand < Cuprum::Command
         | 
| 25 27 | 
             
                private
         | 
| 26 28 |  | 
| 27 | 
            -
                def process | 
| 29 | 
            +
                def process(value = nil)
         | 
| 28 30 | 
             
                  value
         | 
| 29 | 
            -
                end | 
| 30 | 
            -
              end | 
| 31 | 
            -
            end | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cuprum/built_in/identity_command'
         | 
| 2 4 | 
             
            require 'cuprum/operation'
         | 
| 3 5 |  | 
| @@ -23,5 +25,5 @@ module Cuprum::BuiltIn | |
| 23 25 | 
             
              #   #=> ['errors.messages.unknown']
         | 
| 24 26 | 
             
              class IdentityOperation < Cuprum::BuiltIn::IdentityCommand
         | 
| 25 27 | 
             
                include Cuprum::Operation::Mixin
         | 
| 26 | 
            -
              end | 
| 27 | 
            -
            end | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cuprum/built_in'
         | 
| 2 4 | 
             
            require 'cuprum/command'
         | 
| 3 5 |  | 
| @@ -13,6 +15,6 @@ module Cuprum::BuiltIn | |
| 13 15 | 
             
              class NullCommand < Cuprum::Command
         | 
| 14 16 | 
             
                private
         | 
| 15 17 |  | 
| 16 | 
            -
                def process | 
| 17 | 
            -
              end | 
| 18 | 
            -
            end | 
| 18 | 
            +
                def process(*_args); end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cuprum/built_in/null_command'
         | 
| 2 4 | 
             
            require 'cuprum/operation'
         | 
| 3 5 |  | 
| @@ -12,5 +14,5 @@ module Cuprum::BuiltIn | |
| 12 14 | 
             
              #   #=> true
         | 
| 13 15 | 
             
              class NullOperation < Cuprum::BuiltIn::NullCommand
         | 
| 14 16 | 
             
                include Cuprum::Operation::Mixin
         | 
| 15 | 
            -
              end | 
| 16 | 
            -
            end | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/lib/cuprum/command.rb
    CHANGED
    
    | @@ -1,5 +1,8 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/currying'
         | 
| 2 4 | 
             
            require 'cuprum/processing'
         | 
| 5 | 
            +
            require 'cuprum/steps'
         | 
| 3 6 |  | 
| 4 7 | 
             
            module Cuprum
         | 
| 5 8 | 
             
              # Functional object that encapsulates a business logic operation with a
         | 
| @@ -18,14 +21,14 @@ module Cuprum | |
| 18 21 | 
             
              #   class MultiplyCommand < Cuprum::Command
         | 
| 19 22 | 
             
              #     def initialize multiplier
         | 
| 20 23 | 
             
              #       @multiplier = multiplier
         | 
| 21 | 
            -
              #     end | 
| 24 | 
            +
              #     end
         | 
| 22 25 | 
             
              #
         | 
| 23 26 | 
             
              #     private
         | 
| 24 27 | 
             
              #
         | 
| 25 28 | 
             
              #     def process int
         | 
| 26 29 | 
             
              #       int * @multiplier
         | 
| 27 | 
            -
              #     end | 
| 28 | 
            -
              #   end | 
| 30 | 
            +
              #     end
         | 
| 31 | 
            +
              #   end
         | 
| 29 32 | 
             
              #
         | 
| 30 33 | 
             
              #   triple_command = MultiplyCommand.new(3)
         | 
| 31 34 | 
             
              #   result         = command_command.call(5)
         | 
| @@ -36,7 +39,7 @@ module Cuprum | |
| 36 39 | 
             
              #   class DivideCommand < Cuprum::Command
         | 
| 37 40 | 
             
              #     def initialize divisor
         | 
| 38 41 | 
             
              #       @divisor = divisor
         | 
| 39 | 
            -
              #     end | 
| 42 | 
            +
              #     end
         | 
| 40 43 | 
             
              #
         | 
| 41 44 | 
             
              #     private
         | 
| 42 45 | 
             
              #
         | 
| @@ -46,8 +49,8 @@ module Cuprum | |
| 46 49 | 
             
              #       end
         | 
| 47 50 | 
             
              #
         | 
| 48 51 | 
             
              #       int / @divisor
         | 
| 49 | 
            -
              #     end | 
| 50 | 
            -
              #   end | 
| 52 | 
            +
              #     end
         | 
| 53 | 
            +
              #   end
         | 
| 51 54 | 
             
              #
         | 
| 52 55 | 
             
              #   halve_command = DivideCommand.new(2)
         | 
| 53 56 | 
             
              #   result        = halve_command.call(10)
         | 
| @@ -61,57 +64,11 @@ module Cuprum | |
| 61 64 | 
             
              #   result.error #=> 'errors.messages.divide_by_zero'
         | 
| 62 65 | 
             
              #   result.value #=> nil
         | 
| 63 66 | 
             
              #
         | 
| 64 | 
            -
              # @example Command Chaining
         | 
| 65 | 
            -
              #   class AddCommand < Cuprum::Command
         | 
| 66 | 
            -
              #     def initialize addend
         | 
| 67 | 
            -
              #       @addend = addend
         | 
| 68 | 
            -
              #     end # constructor
         | 
| 69 | 
            -
              #
         | 
| 70 | 
            -
              #     private
         | 
| 71 | 
            -
              #
         | 
| 72 | 
            -
              #     def process int
         | 
| 73 | 
            -
              #       int + @addend
         | 
| 74 | 
            -
              #     end # method process
         | 
| 75 | 
            -
              #   end # class
         | 
| 76 | 
            -
              #
         | 
| 77 | 
            -
              #   double_and_add_one = MultiplyCommand.new(2).chain(AddCommand.new(1))
         | 
| 78 | 
            -
              #   result             = double_and_add_one(5)
         | 
| 79 | 
            -
              #
         | 
| 80 | 
            -
              #   result.value #=> 5
         | 
| 81 | 
            -
              #
         | 
| 82 | 
            -
              # @example Conditional Chaining
         | 
| 83 | 
            -
              #   class EvenCommand < Cuprum::Command
         | 
| 84 | 
            -
              #     private
         | 
| 85 | 
            -
              #
         | 
| 86 | 
            -
              #     def process int
         | 
| 87 | 
            -
              #       return int if int.even?
         | 
| 88 | 
            -
              #
         | 
| 89 | 
            -
              #       Cuprum::Errors.new(error: 'errors.messages.not_even')
         | 
| 90 | 
            -
              #     end # method process
         | 
| 91 | 
            -
              #   end # class
         | 
| 92 | 
            -
              #
         | 
| 93 | 
            -
              #   # The next step in a Collatz sequence is determined as follows:
         | 
| 94 | 
            -
              #   # - If the number is even, divide it by 2.
         | 
| 95 | 
            -
              #   # - If the number is odd, multiply it by 3 and add 1.
         | 
| 96 | 
            -
              #   collatz_command =
         | 
| 97 | 
            -
              #     EvenCommand.new.
         | 
| 98 | 
            -
              #       chain(DivideCommand.new(2), :on => :success).
         | 
| 99 | 
            -
              #       chain(
         | 
| 100 | 
            -
              #         MultiplyCommand.new(3).chain(AddCommand.new(1),
         | 
| 101 | 
            -
              #         :on => :failure
         | 
| 102 | 
            -
              #       )
         | 
| 103 | 
            -
              #
         | 
| 104 | 
            -
              #   result = collatz_command.new(5)
         | 
| 105 | 
            -
              #   result.value #=> 16
         | 
| 106 | 
            -
              #
         | 
| 107 | 
            -
              #   result = collatz_command.new(16)
         | 
| 108 | 
            -
              #   result.value #=> 8
         | 
| 109 | 
            -
              #
         | 
| 110 | 
            -
              # @see Cuprum::Chaining
         | 
| 111 67 | 
             
              # @see Cuprum::Processing
         | 
| 112 68 | 
             
              class Command
         | 
| 113 69 | 
             
                include Cuprum::Processing
         | 
| 114 | 
            -
                include Cuprum:: | 
| 70 | 
            +
                include Cuprum::Currying
         | 
| 71 | 
            +
                include Cuprum::Steps
         | 
| 115 72 |  | 
| 116 73 | 
             
                # Returns a new instance of Cuprum::Command.
         | 
| 117 74 | 
             
                #
         | 
| @@ -119,12 +76,33 @@ module Cuprum | |
| 119 76 | 
             
                #   #call method will wrap the block and set the result #value to the return
         | 
| 120 77 | 
             
                #   value of the block. This overrides the implementation in #process, if
         | 
| 121 78 | 
             
                #   any.
         | 
| 122 | 
            -
                def initialize | 
| 79 | 
            +
                def initialize(&implementation)
         | 
| 123 80 | 
             
                  return unless implementation
         | 
| 124 81 |  | 
| 125 82 | 
             
                  define_singleton_method :process, &implementation
         | 
| 126 83 |  | 
| 127 84 | 
             
                  singleton_class.send(:private, :process)
         | 
| 128 | 
            -
                end | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # (see Cuprum::Processing#call)
         | 
| 88 | 
            +
                def call(*args, **kwargs, &block)
         | 
| 89 | 
            +
                  steps { super }
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # Wraps the command in a proc.
         | 
| 93 | 
            +
                #
         | 
| 94 | 
            +
                # Calling the proc will call the command with the given arguments, keywords,
         | 
| 95 | 
            +
                # and block.
         | 
| 96 | 
            +
                #
         | 
| 97 | 
            +
                # @return [Proc] the wrapping proc.
         | 
| 98 | 
            +
                def to_proc
         | 
| 99 | 
            +
                  @to_proc ||= lambda do |*args, **kwargs, &block|
         | 
| 100 | 
            +
                    if kwargs.empty?
         | 
| 101 | 
            +
                      call(*args, &block)
         | 
| 102 | 
            +
                    else
         | 
| 103 | 
            +
                      call(*args, **kwargs, &block)
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cuprum'
         | 
| 2 4 |  | 
| 3 5 | 
             
            require 'sleeping_king_studios/tools/toolbelt'
         | 
| @@ -28,7 +30,7 @@ module Cuprum | |
| 28 30 | 
             
              #
         | 
| 29 31 | 
             
              #   factory::Dream #=> DreamCommand
         | 
| 30 32 | 
             
              #   factory.dream  #=> an instance of DreamCommand
         | 
| 31 | 
            -
              class CommandFactory < Module
         | 
| 33 | 
            +
              class CommandFactory < Module # rubocop:disable Metrics/ClassLength
         | 
| 32 34 | 
             
                # Defines the Domain-Specific Language and helper methods for dynamically
         | 
| 33 35 | 
             
                # defined commands.
         | 
| 34 36 | 
             
                class << self
         | 
| @@ -147,20 +149,14 @@ module Cuprum | |
| 147 149 | 
             
                  def command_class(name, **metadata, &defn)
         | 
| 148 150 | 
             
                    guard_abstract_factory!
         | 
| 149 151 |  | 
| 150 | 
            -
                    raise ArgumentError, 'must provide a block' | 
| 152 | 
            +
                    raise ArgumentError, 'must provide a block' unless block_given?
         | 
| 151 153 |  | 
| 152 | 
            -
                     | 
| 154 | 
            +
                    method_name = normalize_command_name(name)
         | 
| 153 155 |  | 
| 154 | 
            -
                    (@command_definitions ||= {})[ | 
| 156 | 
            +
                    (@command_definitions ||= {})[method_name] =
         | 
| 155 157 | 
             
                      metadata.merge(__const_defn__: defn)
         | 
| 156 158 |  | 
| 157 | 
            -
                     | 
| 158 | 
            -
             | 
| 159 | 
            -
                    define_method(name) do |*args, &block|
         | 
| 160 | 
            -
                      command_class = const_get(const_name)
         | 
| 161 | 
            -
             | 
| 162 | 
            -
                      build_command(command_class, *args, &block)
         | 
| 163 | 
            -
                    end
         | 
| 159 | 
            +
                    define_lazy_command_method(method_name)
         | 
| 164 160 | 
             
                  end
         | 
| 165 161 |  | 
| 166 162 | 
             
                  protected
         | 
| @@ -184,21 +180,47 @@ module Cuprum | |
| 184 180 |  | 
| 185 181 | 
             
                    (@command_definitions ||= {})[command_name] = metadata
         | 
| 186 182 |  | 
| 187 | 
            -
                    define_method(command_name) do |*args|
         | 
| 188 | 
            -
                       | 
| 183 | 
            +
                    define_method(command_name) do |*args, **kwargs|
         | 
| 184 | 
            +
                      if kwargs.empty?
         | 
| 185 | 
            +
                        instance_exec(*args, &builder)
         | 
| 186 | 
            +
                      else
         | 
| 187 | 
            +
                        instance_exec(*args, **kwargs, &builder)
         | 
| 188 | 
            +
                      end
         | 
| 189 189 | 
             
                    end
         | 
| 190 190 | 
             
                  end
         | 
| 191 191 |  | 
| 192 192 | 
             
                  def define_command_from_class(command_class, name:, metadata: {})
         | 
| 193 193 | 
             
                    guard_invalid_definition!(command_class)
         | 
| 194 194 |  | 
| 195 | 
            -
                     | 
| 195 | 
            +
                    method_name = normalize_command_name(name)
         | 
| 196 196 |  | 
| 197 | 
            -
                    (@command_definitions ||= {})[ | 
| 197 | 
            +
                    (@command_definitions ||= {})[method_name] =
         | 
| 198 198 | 
             
                      metadata.merge(__const_defn__: command_class)
         | 
| 199 199 |  | 
| 200 | 
            -
                     | 
| 201 | 
            -
             | 
| 200 | 
            +
                    define_command_method(method_name, command_class)
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  def define_command_method(method_name, command_class)
         | 
| 204 | 
            +
                    define_method(method_name) do |*args, **kwargs, &block|
         | 
| 205 | 
            +
                      if kwargs.empty?
         | 
| 206 | 
            +
                        build_command(command_class, *args, &block)
         | 
| 207 | 
            +
                      else
         | 
| 208 | 
            +
                        build_command(command_class, *args, **kwargs, &block)
         | 
| 209 | 
            +
                      end
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                  def define_lazy_command_method(method_name)
         | 
| 214 | 
            +
                    const_name = tools.string_tools.camelize(method_name)
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                    define_method(method_name) do |*args, **kwargs, &block|
         | 
| 217 | 
            +
                      command_class = const_get(const_name)
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                      if kwargs.empty?
         | 
| 220 | 
            +
                        build_command(command_class, *args, &block)
         | 
| 221 | 
            +
                      else
         | 
| 222 | 
            +
                        build_command(command_class, *args, **kwargs, &block)
         | 
| 223 | 
            +
                      end
         | 
| 202 224 | 
             
                    end
         | 
| 203 225 | 
             
                  end
         | 
| 204 226 |  | 
| @@ -207,21 +229,21 @@ module Cuprum | |
| 207 229 |  | 
| 208 230 | 
             
                    raise NotImplementedError,
         | 
| 209 231 | 
             
                      'Cuprum::CommandFactory is an abstract class. Create a subclass to ' \
         | 
| 210 | 
            -
                      'define commands for a factory.' | 
| 232 | 
            +
                      'define commands for a factory.'
         | 
| 211 233 | 
             
                  end
         | 
| 212 234 |  | 
| 213 235 | 
             
                  def guard_invalid_definition!(command_class)
         | 
| 214 236 | 
             
                    return if command_class.is_a?(Class) && command_class < Cuprum::Command
         | 
| 215 237 |  | 
| 216 | 
            -
                    raise ArgumentError, 'definition must be a command class' | 
| 238 | 
            +
                    raise ArgumentError, 'definition must be a command class'
         | 
| 217 239 | 
             
                  end
         | 
| 218 240 |  | 
| 219 241 | 
             
                  def normalize_command_name(command_name)
         | 
| 220 | 
            -
                    tools. | 
| 242 | 
            +
                    tools.string_tools.underscore(command_name).intern
         | 
| 221 243 | 
             
                  end
         | 
| 222 244 |  | 
| 223 245 | 
             
                  def require_definition!
         | 
| 224 | 
            -
                    raise ArgumentError, 'must provide a command class or a block' | 
| 246 | 
            +
                    raise ArgumentError, 'must provide a command class or a block'
         | 
| 225 247 | 
             
                  end
         | 
| 226 248 |  | 
| 227 249 | 
             
                  def tools
         | 
| @@ -243,7 +265,7 @@ module Cuprum | |
| 243 265 | 
             
                end
         | 
| 244 266 |  | 
| 245 267 | 
             
                # @private
         | 
| 246 | 
            -
                def const_defined?(const_name, inherit = true)
         | 
| 268 | 
            +
                def const_defined?(const_name, inherit = true) # rubocop:disable Style/OptionalBooleanParameter
         | 
| 247 269 | 
             
                  command?(const_name) || super
         | 
| 248 270 | 
             
                end
         | 
| 249 271 |  | 
| @@ -265,8 +287,12 @@ module Cuprum | |
| 265 287 |  | 
| 266 288 | 
             
                private
         | 
| 267 289 |  | 
| 268 | 
            -
                def build_command(command_class, *args, &block)
         | 
| 269 | 
            -
                   | 
| 290 | 
            +
                def build_command(command_class, *args, **kwargs, &block)
         | 
| 291 | 
            +
                  if kwargs.empty?
         | 
| 292 | 
            +
                    command_class.new(*args, &block)
         | 
| 293 | 
            +
                  else
         | 
| 294 | 
            +
                    command_class.new(*args, **kwargs, &block)
         | 
| 295 | 
            +
                  end
         | 
| 270 296 | 
             
                end
         | 
| 271 297 |  | 
| 272 298 | 
             
                def normalize_command_name(command_name)
         | 
| @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cuprum
         | 
| 6 | 
            +
              # Implements partial application for command objects.
         | 
| 7 | 
            +
              #
         | 
| 8 | 
            +
              # Partial application (more commonly referred to, if imprecisely, as currying)
         | 
| 9 | 
            +
              # refers to fixing some number of arguments to a function, resulting in a
         | 
| 10 | 
            +
              # function with a smaller number of arguments.
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              # In Cuprum's case, a curried (partially applied) command takes an original
         | 
| 13 | 
            +
              # command and pre-defines some of its arguments. When the curried command is
         | 
| 14 | 
            +
              # called, the predefined arguments and/or keywords will be combined with the
         | 
| 15 | 
            +
              # arguments passed to #call.
         | 
| 16 | 
            +
              #
         | 
| 17 | 
            +
              # @example Currying Arguments
         | 
| 18 | 
            +
              #   # Our base command takes two arguments.
         | 
| 19 | 
            +
              #   say_command = Cuprum::Command.new do |greeting, person|
         | 
| 20 | 
            +
              #     "#{greeting}, #{person}!"
         | 
| 21 | 
            +
              #   end
         | 
| 22 | 
            +
              #   say_command.call('Hello', 'world')
         | 
| 23 | 
            +
              #   #=> returns a result with value 'Hello, world!'
         | 
| 24 | 
            +
              #
         | 
| 25 | 
            +
              #   # Next, we create a curried command. This sets the first argument to
         | 
| 26 | 
            +
              #   # always be 'Greetings', so our curried command only takes one argument,
         | 
| 27 | 
            +
              #   # namely the name of the person being greeted.
         | 
| 28 | 
            +
              #   greet_command = say_command.curry('Greetings')
         | 
| 29 | 
            +
              #   greet_command.call('programs')
         | 
| 30 | 
            +
              #   #=> returns a result with value 'Greetings, programs!'
         | 
| 31 | 
            +
              #
         | 
| 32 | 
            +
              #   # Here, we are creating a curried command that passes both arguments.
         | 
| 33 | 
            +
              #   # Therefore, our curried command does not take any arguments.
         | 
| 34 | 
            +
              #   recruit_command = say_command.curry('Greetings', 'starfighter')
         | 
| 35 | 
            +
              #   recruit_command.call
         | 
| 36 | 
            +
              #   #=> returns a result with value 'Greetings, starfighter!'
         | 
| 37 | 
            +
              #
         | 
| 38 | 
            +
              # @example Currying Keywords
         | 
| 39 | 
            +
              #   # Our base command takes two keywords: a math operation and an array of
         | 
| 40 | 
            +
              #   # integers.
         | 
| 41 | 
            +
              #   math_command = Cuprum::Command.new do |operands:, operation:|
         | 
| 42 | 
            +
              #     operations.reduce(&operation)
         | 
| 43 | 
            +
              #   end
         | 
| 44 | 
            +
              #   math_command.call(operands: [2, 2], operation: :+)
         | 
| 45 | 
            +
              #   #=> returns a result with value 4
         | 
| 46 | 
            +
              #
         | 
| 47 | 
            +
              #   # Our curried command still takes two keywords, but now the operation
         | 
| 48 | 
            +
              #   # keyword is optional. It now defaults to :*, for multiplication.
         | 
| 49 | 
            +
              #   multiply_command = math_command.curry(operation: :*)
         | 
| 50 | 
            +
              #   multiply_command.call(operands: [3, 3])
         | 
| 51 | 
            +
              #   #=> returns a result with value 9
         | 
| 52 | 
            +
              module Currying
         | 
| 53 | 
            +
                autoload :CurriedCommand, 'cuprum/currying/curried_command'
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # Returns a CurriedCommand that wraps this command with pre-set arguments.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # When the curried command is called, the predefined arguments and/or
         | 
| 58 | 
            +
                # keywords will be combined with the arguments passed to #call.
         | 
| 59 | 
            +
                #
         | 
| 60 | 
            +
                # The original command is unchanged.
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @param arguments [Array] The arguments to pass to the curried command.
         | 
| 63 | 
            +
                # @param keywords [Hash] The keywords to pass to the curried command.
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # @return [Cuprum::Currying::CurriedCommand] the curried command.
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                # @see Cuprum::Currying::CurriedCommand#call
         | 
| 68 | 
            +
                def curry(*arguments, **keywords, &block)
         | 
| 69 | 
            +
                  return self if arguments.empty? && keywords.empty? && block.nil?
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  Cuprum::Currying::CurriedCommand.new(
         | 
| 72 | 
            +
                    arguments: arguments,
         | 
| 73 | 
            +
                    block:     block,
         | 
| 74 | 
            +
                    command:   self,
         | 
| 75 | 
            +
                    keywords:  keywords
         | 
| 76 | 
            +
                  )
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         |