class Opto::Option

What is an option? It's like a variable that has a value, which can be validated or manipulated on creation. The value can be resolved from a number of origins, such as an environment variable or random string generator.

Attributes

default[RW]
description[RW]
from[R]
group[R]
initial_value[R]
label[RW]
name[RW]
only_if[R]
required[RW]
skip_if[R]
to[R]
type[RW]
type_options[R]

Public Class Methods

new(options = {}) click to toggle source

Initialize an instance of Opto::Option @param [Hash] options

@option [String] :name Option name
@option [String,Symbol] :type Option type, such as :integer, :string, :boolean, :enum
@option [String] :label A label for this field, to be used in for example an interactive prompt
@option [String] :description Same as label, but more detailed
@option [*] :default Default value for option
@option [String,Symbol,Array<String,Symbol,Hash>,Hash] :from Resolver origins
@option [String,Symbol,Array<String,Symbol,Hash>,Hash] :to Setter targets
@option [String,Symbol,Array<String,Symbol,Hash>,Hash] :skip_if Conditionals that define if this option should be skipped
@option [String,Symbol,Array<String,Symbol,Hash>,Hash] :only_if Conditionals that define if this option should be included
@option [Opto::Group] :group Parent group reference
@option [...] Type definition options, such as { min_length: 3, strip: true }

@example Create an option

Opto::Option.new(
  name: 'cat_name',
  type: 'string',
  label: 'Name of your Cat',
  required: true,
  description: 'Enter a name for your cat',
  from:
    env: 'CAT_NAME'
  only_if:
    pet: 'cat'
  min_length: 2
  max_length: 20
)

@example Create a random string

Opto::Option.new(
  name: 'random_string',
  type: :string,
  from:
    random_string:
      length: 20
      charset: ascii_printable
)
# File lib/opto/option.rb, line 68
def initialize(options = {})
  opts           = options.dup

  @group         = opts.delete(:group)
  if @group && @group.defaults
    opts = @group.defaults.reject{|k,_| [:from, :to].include?(k)}.merge(opts)
  end

  @name          = opts.delete(:name).to_s

  type           = opts.delete(:type)
  @type          = type.to_s.snakecase unless type.nil?

  @label         = opts.delete(:label) || @name
  @description   = opts.delete(:description)
  @default       = opts.delete(:default)
  val            = opts.delete(:value)
  @skip_if       = opts.delete(:skip_if)
  @only_if       = opts.delete(:only_if)
  @from          = normalize_from_to(opts.delete(:from))
  @to            = normalize_from_to(opts.delete(:to))
  validations    = opts.delete(:validate).to_h
  transforms     = opts.delete(:transform)
  transforms     = case transforms
                   when NilClass then {}
                   when Hash then transforms
                   when Array then
                     transforms.each_with_object({}) { |t, hash| hash[t] = true }
                   else
                     raise TypeError, 'Transform has to be a hash or an array'
                   end
  @type_options  = opts.merge(validations).merge(transforms)
  @tried_resolve = false

  set_initial(val) if val
  deep_merge_defaults
end

Public Instance Methods

deep_merge_defaults() click to toggle source
# File lib/opto/option.rb, line 110
def deep_merge_defaults
  return nil unless group && group.defaults
  if group.defaults[:from]
    normalize_from_to(group.defaults[:from]).each do |k,v|
      from[k] ||= v
    end
  end
  if group.defaults[:to]
    normalize_from_to(group.defaults[:to]).each do |k,v|
      to[k] ||= v
    end
  end
end
errors() click to toggle source

Validation errors @return [Hash]

# File lib/opto/option.rb, line 285
def errors
  handler.errors
end
handler() click to toggle source

Access the Opto::Type handler for this option @return [Opto::Type]

# File lib/opto/option.rb, line 188
def handler
  @handler ||= Type.for(type).new(type_options)
rescue StandardError => ex
  raise ex, "#{name}: #{ex.message}"
end
has_group?() click to toggle source
# File lib/opto/option.rb, line 106
def has_group?
  !group.nil?
end
normalize_from_to(inputs) click to toggle source
# File lib/opto/option.rb, line 289
def normalize_from_to(inputs)
  case inputs
  when ::Array
    case inputs.first
    when String, Symbol
      inputs.each_with_object({}) { |o, hash| hash[o.to_s.snakecase.to_sym] = name }
    when Hash
      inputs.each_with_object({}) { |o, hash| o.each { |k,v| hash[k.to_s.snakecase.to_sym] = v } }
    when NilClass
      {}
    else
      raise TypeError, "Invalid format #{inputs.inspect}"
    end
  when Hash
    inputs.each_with_object({}) { |(k, v), hash| hash[k.to_s.snakecase.to_sym] = v }
  when String, Symbol
    { inputs.to_s.snakecase.to_sym => name }
  when NilClass
    {}
  else
    raise TypeError, "Invalid format #{inputs.inspect}"
  end
end
output() click to toggle source

Run setters

# File lib/opto/option.rb, line 257
def output
  setters.each do |setter_config|
    begin
      setter = setter_config[:setter]
      if setter.respond_to?(:call)
        setter.call(setter_config[:hint], value, self)
      else
        setter.new(setter_config[:hint], self).set(value)
      end
    rescue StandardError => ex
      raise ex, "Setter '#{setter_config[:target]}' for '#{name}' : #{ex.message}"
    end
  end
end
required?() click to toggle source

True if this field is defined as required: true @return [Boolean]

# File lib/opto/option.rb, line 215
def required?
  handler.required?
end
resolve() click to toggle source

Run resolvers @raise [TypeError, ArgumentError]

# File lib/opto/option.rb, line 233
def resolve
  return nil if tried_resolve?
  resolvers.each do |resolver_config|
    begin
      resolver = resolver_config[:resolver]
      if resolver.respond_to?(:call)
        result = resolver.call(resolver_config[:hint], self)
      else
        result = resolver.new(resolver_config[:hint], self).resolve
      end
    rescue StandardError => ex
      raise ex, "Resolver '#{resolver_config[:origin]}' for '#{name}' : #{ex.message}"
    end
    unless result.nil?
      @origin = resolver_config[:origin]
      return result
    end
  end
  nil
ensure
  set_tried
end
resolvers() click to toggle source

Accessor to defined resolvers for this option. @return [Array<Opto::Resolver>]

# File lib/opto/option.rb, line 205
def resolvers
  @resolvers ||= from.merge(default: self).map { |origin, hint| { origin: origin, hint: hint, resolver: ((has_group? && group.resolvers[origin]) || Resolver.for(origin)) } }
end
set(value) click to toggle source

Set option value. Also aliased as value= @param value

# File lib/opto/option.rb, line 153
def set(value)
  @value = handler.sanitize(value)
  validate
  @value
end
Also aliased as: value=
set_tried() click to toggle source
# File lib/opto/option.rb, line 223
def set_tried
  @tried_resolve = true
end
setters() click to toggle source
# File lib/opto/option.rb, line 209
def setters
  @setters ||= to.map { |target, hint| { target: target, hint: hint, setter: ((has_group? && group.setters[target]) || Setter.for(target)) } }
end
skip?() click to toggle source

Returns true if this field should not be processed because of the conditionals @return [Boolean]

# File lib/opto/option.rb, line 163
def skip?
  return false unless has_group?
  return true if group.any_true?(skip_if)
  return true unless group.all_true?(only_if)
  false
end
to_h(with_errors: false, with_value: true) click to toggle source

Hash representation of Opto::Option. Can be passed back to Opto::Option.new @param [Boolean] with_errors Include possible validation errors hash @param [Boolean] with_value Include current value @return [Hash]

# File lib/opto/option.rb, line 128
def to_h(with_errors: false, with_value: true)
  hash = {
    name: name,
    label: label,
    type: type,
    description: description,
    default: default,
    from: from.reject { |k,_| k == :default},
    to: to
  }.merge(type_options).reject { |_,v| v.nil? }
  hash[:skip_if] = skip_if if skip_if
  hash[:only_if] = only_if if only_if
  hash[:errors]  = errors  if with_errors
  if with_value
    if type == 'group'
      hash[:value]   = value.to_h(with_values: true, with_errors: with_errors)
    else
      hash[:value] = value
    end
  end
  hash
end
tried_resolve?() click to toggle source
# File lib/opto/option.rb, line 219
def tried_resolve?
  @tried_resolve
end
true?() click to toggle source
# File lib/opto/option.rb, line 279
def true?
  handler.truthy?(value)
end
unset_tried!() click to toggle source
# File lib/opto/option.rb, line 227
def unset_tried!
  @tried_resolve = false
end
valid?() click to toggle source

True if value is valid @return [Boolean]

# File lib/opto/option.rb, line 274
def valid?
  return true if skip?
  handler.valid?(value)
end
validate() click to toggle source

Run validators @raise [TypeError, ArgumentError]

# File lib/opto/option.rb, line 180
def validate
  handler.validate(@value)
rescue StandardError => ex
  raise ex, "Validation for #{name} : #{ex.message}"
end
value() click to toggle source

The value of this option. Will try to run resolvers. @return option_value

# File lib/opto/option.rb, line 196
def value
  return @value unless @value.nil?
  return nil if skip?
  set(resolve)
  @value
end
value=(value)
Alias for: set
value_of(option_name) click to toggle source

Get a value of another Opto::Group member @param [String] option_name

# File lib/opto/option.rb, line 172
def value_of(option_name)
  return value if option_name == self.name
  return nil unless has_group?
  group.value_of(option_name)
end

Private Instance Methods

set_initial(value) click to toggle source
# File lib/opto/option.rb, line 315
def set_initial(value)
  return nil if value.nil?
  @origin = :initial
  set(value)
end