grange-insurance/cuke_commander

View on GitHub
lib/cuke_commander/cl_generator.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module CukeCommander

  # The object responsible for generating Cucumber command lines.
  class CLGenerator # rubocop:disable Metrics/ClassLength # It's mostly just a couple of large hashes

    # Generates a Cucumber command line.
    #
    # Most option values are either an Array or a boolean value. In the case of the former, a
    # String can be used instead of an Array when only a single value is needed. Some option
    # values come in pairs (e.g. formatters and their output locations). These option values
    # are taken as a Hash where a key is the first part of the pair and the key's value is the
    # second part.
    #
    # @param options [Hash] the Cucumber options that the command line should include.
    def generate_command_line(options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/LineLength # It's as good as it's going to get right now
      validate_options(options)
      command_line = 'cucumber'

      options.each_pair do |option, values|
        next if (values == false) || # Don't append options that are explicitly rejected
                (option == :formatters) || # Formatters can have flag pairs, so they're handled separately
                (option == :long_flags) # Not actually an option, just a modifier for the rest of the options

        # Flag only options come in with a true value but don't have a value that needs to be added to the command line
        values = nil if values == true

        append_options(command_line,
                       values: wrap_values(values),
                       flag:   flag_for(option, options[:long_flags]))
      end

      if options[:formatters]
        options[:formatters].each do |format, output_location|
          append_option(command_line,
                        value: format,
                        flag:  flag_for(:format, options[:long_flags]))

          # Not all formatters come will also have an associated output redirection
          next if output_location.to_s.empty?

          append_option(command_line,
                        value: output_location,
                        flag:  flag_for(:output, options[:long_flags]))
        end
      end

      command_line
    end


    private


    def validate_options(options)
      raise(ArgumentError, "Argument must be a Hash, got: #{options.class}") unless options.is_a?(Hash)

      options.each_pair do |option, value|
        raise(ArgumentError, "Option: #{option}, is not a cucumber option") unless CUKE_OPTIONS.include?(option.to_s)

        validate_option(option, value)
      end
    end

    def validate_option(option_name, value) # rubocop:disable Metrics/MethodLength # It's just a big hash
      error_info = { profiles:   ['Profiles', 'a String or Array'],
                     tags:       ['Tags', 'a String or Array'],
                     file_paths: ['File path', 'a String or Array'],
                     formatters: ['Formatters', 'a Hash'],
                     excludes:   ['Excludes', 'a String or Array'],
                     no_source:  ['No-source', 'true or false'],
                     no_color:   ['No-color', 'true or false'],
                     color:      ['Color', 'true or false'],
                     backtrace:  ['Backtrace', 'true or false'],
                     expand:     ['Expand', 'true or false'],
                     guess:      ['Guess', 'true or false'],
                     help:       ['Help', 'true or false'],
                     dry_run:    ['Dry run', 'true or false'],
                     no_profile: ['No profile', 'true or false'],
                     quiet:      ['Quiet', 'true or false'],
                     strict:     ['Strict', 'true or false'],
                     verbose:    ['Verbose', 'true or false'],
                     version:    ['Version', 'true or false'],
                     wip:        ['Wip', 'true or false'],
                     names:      ['Names', 'a String or Array'],
                     requires:   ['Requires', 'a String or Array'],
                     options:    ['Options', 'a String or Array'],
                     long_flags: ['Long Flags', 'true or false'] }

      return if send("valid_#{option_name}?", value)

      raise_invalid_error(error_info[option_name].first, error_info[option_name].last, value)
    end

    def append_options(command, values: nil, flag: nil)
      values.each do |value|
        append_option(command, flag: flag, value: value)
      end
    end

    def append_option(command, flag: nil, value: nil)
      command << " #{flag}" if flag
      command << " #{value}" if value
    end

    def wrap_values(value_set)
      value_set.is_a?(Array) ? value_set : [value_set]
    end

    def raise_invalid_error(option, valid_types, value_used)
      raise(ArgumentError, "#{option} option must be #{valid_types}, got: #{value_used.class}")
    end

    %w[profiles tags file_paths excludes names requires options].each do |option|
      define_method("valid_#{option}?") { |check_this| valid_string_array_value?(check_this) }
    end

    %w[no_source no_color color backtrace wip no_profile expand
       strict verbose version quiet guess help dry_run long_flags].each do |option|
      define_method("valid_#{option}?") { |check_this| valid_boolean_value?(check_this) }
    end

    def valid_formatters?(formatters)
      valid_hash_value?(formatters)
    end

    def valid_string_array_value?(value)
      value.nil? || value.is_a?(Array) || value.is_a?(String)
    end

    def valid_boolean_value?(value)
      value.nil? || (value == true) || (value == false)
    end

    def valid_hash_value?(value)
      value.nil? || value.is_a?(Hash)
    end

    def flag_for(option, long_flags) # rubocop:disable Metrics/MethodLength # It's just a big hash
      flags = { tags:       ['--tags', '-t'],
                profiles:   ['--profile', '-p'],
                names:      ['--name', '-n'],
                excludes:   ['--exclude', '-e'],
                requires:   ['--require', '-r'],
                backtrace:  ['--backtrace', '-b'],
                color:      ['--color', '-c'],
                no_color:   ['--no-color', '--no-color'],
                dry_run:    ['--dry-run', '-d'],
                guess:      ['--guess', '-g'],
                wip:        ['--wip', '-w'],
                quiet:      ['--quiet', '-q'],
                help:       ['--help', '-h'],
                verbose:    ['--verbose', '-v'],
                version:    ['--version', '--version'],
                strict:     ['--strict', '-S'],
                expand:     ['--expand', '-x'],
                no_source:  ['--no-source', '-s'],
                no_profile: ['--no-profile', '-P'],
                format:     ['--format', '-f'],
                output:     ['--out', '-o'],
                file_paths: [nil, nil], # File paths are appended 'as is', so there is no flag used
                options: [nil, nil] } # Extra options are appended 'as is', so there is no flag used

      if long_flags
        flags[option].first
      else
        flags[option].last
      end
    end

  end
end