wpscanteam/OptParseValidator

View on GitHub
lib/opt_parse_validator/opts/multi_choices.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

module OptParseValidator
  # Implementation of the MultiChoices Option
  class OptMultiChoices < OptArray
    # @param [ Array ] option See OptBase#new
    # @param [ Hash ] attrs
    # @option attrs [ Hash ] :choices
    # @option attrs [ Array<Array> ] :incompatible
    # @options attrs [ String ] :separator See OptArray#new
    def initialize(option, attrs = {})
      raise Error, 'The :choices attribute is mandatory' unless attrs.key?(:choices)
      raise Error, 'The :choices attribute must be a hash' unless attrs[:choices].is_a?(Hash)

      super(option, attrs)
    end

    def append_help_messages
      option << 'Available Choices:'

      append_choices_help_messages

      super

      append_incompatible_help_messages
    end

    def append_choices_help_messages
      max_spaces = choices.keys.max_by(&:size).size

      choices.each do |key, opt|
        first_line_prefix  = " #{key} #{' ' * (max_spaces - key.length)}"
        other_lines_prefix = ' ' * first_line_prefix.size

        opt_help_messages(opt).each_with_index do |message, index|
          option << "#{index.zero? ? first_line_prefix : other_lines_prefix} #{message}"
        end
      end
    end

    def help_message_for_default
      msg = +''

      default.each do |key, value|
        msg << if value == true
                 key.to_s.titleize
               else
                 "#{key.to_s.titleize}: #{value}"
               end
        msg << ', '
      end

      msg.chomp(', ')
    end

    # @param [ OptBase ] opt
    #
    # @return [ Array<String> ]
    def opt_help_messages(opt)
      opt.help_messages.empty? ? [opt.to_s.humanize] : opt.help_messages
    end

    def append_incompatible_help_messages
      return if incompatible.empty?

      option << 'Incompatible choices (only one of each group/s can be used):'

      incompatible.each do |a|
        option << " - #{a.map(&:to_s).join(', ')}"
      end
    end

    # @param [ String ] value
    #
    # @return [ Hash ]
    def validate(value)
      results = {}

      super(value).each do |item|
        opt = choices[item.to_sym]

        if opt
          opt_value = opt.value_if_empty.nil? ? true : opt.value_if_empty
        else
          opt, opt_value = value_from_pattern(item)
        end

        results[opt.to_sym] = opt.normalize(opt.validate(opt_value))
      end

      verify_compatibility(results)
    end

    # @return [ Array ]
    def value_from_pattern(item)
      choices.each do |key, opt|
        next unless item =~ /\A#{key}(.*)\z/

        return [opt, Regexp.last_match[1]]
      end

      raise Error, "Unknown choice: #{item}"
    end

    # @return [ Array<Array<Symbol>> ]
    def incompatible
      Array(attrs[:incompatible])
    end

    # @param [ Hash ] values
    #
    # @return [ Hash ]
    def verify_compatibility(values)
      incompatible.each do |a|
        last_match = ''

        a.each do |key|
          sym = choices[key].to_sym

          next unless values.key?(sym)

          raise Error, "Incompatible choices detected: #{last_match}, #{key}" unless last_match.empty?

          last_match = key
        end
      end
      values
    end

    # No normalization
    def normalize(value)
      value
    end
  end
end