pyzlnar/aye_commander

View on GitHub
lib/aye_commander/limitable.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
module AyeCommander
  # This module takes care of command arguments being limited by the user
  # specifics.
  module Limitable
    # Limitable is a module which functionality is completely defined at class
    # level.
    module ClassMethods
      LIMITERS = %i[receives requires returns].freeze

      # Contains all the limiters
      def limiters
        @limiters ||= Hash.new([])
      end

      # Helps the command define methods to not use method missing on every
      # instance.
      #
      # The original idea was to encourage to use uses for a small performance
      # boost when running their commands since the methods would be created at
      # load time.
      # This idea has been since scrapped since it would make the commands look
      # very convoluted and the performance hit is probably neglegible since
      # the methods themselves are defined after the first method missing.
      #
      # The functionality however still remains as limited call this method
      # internally
      def uses(*args)
        uses = limiters[:uses]
        return uses if args.empty?

        missing = args - uses
        attr_accessor(*missing) if missing.any?

        limiters[:uses] |= args
      end

      # Defines .receives .requires and .returns
      # .receives Tells the command which arguments are expected to be received
      # .requires Tells the command which arguments are actually required
      # .returns  Tells the command which arguments to return in the result
      LIMITERS.each do |limiter|
        define_method(limiter) do |*args|
          return limiters[__method__] if args.empty?
          uses(*args)
          limiters[__method__] |= args
        end
      end

      # Helper method that tells the result class which methods to create as
      # readers the first time it is created.
      def readers
        [:status] | uses
      end

      # Validates the limiter arguments
      def validate_arguments(args, skip_validations: false)
        if validate_required_arguments?(skip_validations)
          validate_required_arguments(args)
        end

        return unless validate_received_arguments?(skip_validations)
        validate_received_arguments(args)
      end

      # Dont validate if asked to skip or requires is empty
      def validate_required_arguments?(skip_validations)
        return if [true, :requires].include?(skip_validations)
        requires.any?
      end

      # Dont validate if asked to skip or receives is empty
      def validate_received_arguments?(skip_validations)
        return if [true, :receives].include?(skip_validations)
        receives.any?
      end

      # Validates the required arguments
      # Required arguments are ones that your commander absolutely needs to be
      # able to run properly.
      def validate_required_arguments(args)
        missing = requires - args.keys
        raise MissingRequiredArgumentError, missing if missing.any?
      end

      # Validates the received arguments
      # Received arguments are the ones that your command is able to receive.
      # Any other argument not defined by this would be considered an error.
      def validate_received_arguments(args)
        extras = args.keys - (receives | requires)
        raise UnexpectedReceivedArgumentError, extras if extras.any?
      end
    end
  end
end