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
Public Class Methods
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
# 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
Validation errors @return [Hash]
# File lib/opto/option.rb, line 285 def errors handler.errors end
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
# File lib/opto/option.rb, line 106 def has_group? !group.nil? end
# 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
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
True if this field is defined as required: true @return [Boolean]
# File lib/opto/option.rb, line 215 def required? handler.required? end
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
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 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
# File lib/opto/option.rb, line 223 def set_tried @tried_resolve = true end
# 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
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
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
# File lib/opto/option.rb, line 219 def tried_resolve? @tried_resolve end
# File lib/opto/option.rb, line 279 def true? handler.truthy?(value) end
# File lib/opto/option.rb, line 227 def unset_tried! @tried_resolve = false end
True if value is valid @return [Boolean]
# File lib/opto/option.rb, line 274 def valid? return true if skip? handler.valid?(value) end
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
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
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
# File lib/opto/option.rb, line 315 def set_initial(value) return nil if value.nil? @origin = :initial set(value) end