bio-miga/miga

View on GitHub
lib/miga/common/with_option.rb

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
# frozen_string_literal: true

##
# Helper module including specific functions to handle objects that
# have configurable options. The class including this module must implement
# the methods +.OPTIONS+, +#metadata+, and +#save+.
module MiGA::Common::WithOption
  def option(key)
    assert_has_option(key)
    opt = option_by_metadata(key)
    value = opt.nil? ? option_by_default(key) : opt
    value = value[self] if value.is_a?(Proc)
    value
  end

  def set_option(key, value, from_string = false)
    metadata[key] = assert_valid_option_value(key, value, from_string)
    save
    option(key)
  end

  def all_options
    Hash[self.class.OPTIONS.each_key.map { |key| [key, option(key)] }]
  end

  def option?(key)
    !self.class.OPTIONS[key.to_sym].nil?
  end

  def option_by_metadata(key)
    metadata[key]
  end

  def option_by_default(key)
    self.class.OPTIONS[key.to_sym][:default]
  end

  def assert_has_option(key)
    opt = self.class.OPTIONS[key.to_sym]
    raise "Unrecognized option: #{key}" if opt.nil?
    opt
  end

  def assert_valid_option_value(key, value, from_string = false)
    opt = assert_has_option(key)
    value = option_from_string(key, value) if from_string

    # nil is always valid, and so are supported tokens
    return value if value.nil? || opt[:tokens]&.include?(value)

    if opt[:type] && !value.is_a?(opt[:type])
      raise "Invalid value type for #{key}: #{value.class}, not #{opt[:type]}"
    end

    if opt[:in] && !opt[:in].include?(value)
      raise "Value out of range for #{key}: #{value}, not in #{opt[:in]}"
    end

    value
  end

  def option_from_string(key, value)
    opt = assert_has_option(key)

    if ['', 'nil'].include?(value)
      nil
    elsif opt[:tokens]&.include?(value)
      value
    elsif opt[:type]&.equal?(Float)
      raise "Not a float: #{value}" unless value =~ /^-?\.?\d/
      value.to_f
    elsif opt[:type]&.equal?(Integer)
      raise "Not an integer: #{value}" unless value =~ /^-?\d/
      value.to_i
    elsif opt[:in]&.include?(true) && value == 'true'
      true
    elsif opt[:in]&.include?(false) && value == 'false'
      false
    else
      value
    end
  end
end