Class: Opto::Option

Inherits:
Object
  • Object
show all
Defined in:
lib/opto/option.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Option

Initialize an instance of Opto::Option

Examples:

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
)

Create a random string

Opto::Option.new(
  name: 'random_string',
  type: :string,
  from:
    random_string:
      length: 20
      charset: ascii_printable
)

Parameters:

  • options (Hash) (defaults to: {})

    @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 }


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 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

Instance Attribute Details

#defaultObject

Returns the value of attribute default


21
22
23
# File 'lib/opto/option.rb', line 21

def default
  @default
end

#descriptionObject

Returns the value of attribute description


19
20
21
# File 'lib/opto/option.rb', line 19

def description
  @description
end

#fromObject (readonly)

Returns the value of attribute from


22
23
24
# File 'lib/opto/option.rb', line 22

def from
  @from
end

#groupObject (readonly)

Returns the value of attribute group


24
25
26
# File 'lib/opto/option.rb', line 24

def group
  @group
end

#initial_valueObject (readonly)

Returns the value of attribute initial_value


27
28
29
# File 'lib/opto/option.rb', line 27

def initial_value
  @initial_value
end

#labelObject

Returns the value of attribute label


18
19
20
# File 'lib/opto/option.rb', line 18

def label
  @label
end

#nameObject

Returns the value of attribute name


17
18
19
# File 'lib/opto/option.rb', line 17

def name
  @name
end

#only_ifObject (readonly)

Returns the value of attribute only_if


26
27
28
# File 'lib/opto/option.rb', line 26

def only_if
  @only_if
end

#requiredObject

Returns the value of attribute required


20
21
22
# File 'lib/opto/option.rb', line 20

def required
  @required
end

#skip_ifObject (readonly)

Returns the value of attribute skip_if


25
26
27
# File 'lib/opto/option.rb', line 25

def skip_if
  @skip_if
end

#toObject (readonly)

Returns the value of attribute to


23
24
25
# File 'lib/opto/option.rb', line 23

def to
  @to
end

#typeObject

Returns the value of attribute type


16
17
18
# File 'lib/opto/option.rb', line 16

def type
  @type
end

#type_optionsObject (readonly)

Returns the value of attribute type_options


28
29
30
# File 'lib/opto/option.rb', line 28

def type_options
  @type_options
end

Instance Method Details

#deep_merge_defaultsObject


110
111
112
113
114
115
116
117
118
119
120
121
122
# 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

#errorsHash

Validation errors

Returns:

  • (Hash)

285
286
287
# File 'lib/opto/option.rb', line 285

def errors
  handler.errors
end

#handlerOpto::Type

Access the Opto::Type handler for this option

Returns:


188
189
190
191
192
# 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?Boolean

Returns:

  • (Boolean)

106
107
108
# File 'lib/opto/option.rb', line 106

def has_group?
  !group.nil?
end

#normalize_from_to(inputs) ⇒ Object


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# 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

#outputObject

Run setters


257
258
259
260
261
262
263
264
265
266
267
268
269
270
# 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?Boolean

True if this field is defined as required: true

Returns:

  • (Boolean)

215
216
217
# File 'lib/opto/option.rb', line 215

def required?
  handler.required?
end

#resolveObject

Run resolvers

Raises:

  • (TypeError, ArgumentError)

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# 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

#resolversArray<Opto::Resolver>

Accessor to defined resolvers for this option.

Returns:


205
206
207
# 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) ⇒ Object Also known as: value=

Set option value. Also aliased as #value=

Parameters:

  • value

153
154
155
156
157
# File 'lib/opto/option.rb', line 153

def set(value)
  @value = handler.sanitize(value)
  validate
  @value
end

#set_triedObject


223
224
225
# File 'lib/opto/option.rb', line 223

def set_tried
  @tried_resolve = true
end

#settersObject


209
210
211
# 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?Boolean

Returns true if this field should not be processed because of the conditionals

Returns:

  • (Boolean)

163
164
165
166
167
168
# 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) ⇒ Hash

Hash representation of Opto::Option. Can be passed back to Opto::Option.new

Parameters:

  • with_errors (Boolean)

    Include possible validation errors hash

  • with_value (Boolean)

    Include current value

Returns:

  • (Hash)

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# 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?Boolean

Returns:

  • (Boolean)

219
220
221
# File 'lib/opto/option.rb', line 219

def tried_resolve?
  @tried_resolve
end

#true?Boolean

Returns:

  • (Boolean)

279
280
281
# File 'lib/opto/option.rb', line 279

def true?
  handler.truthy?(value)
end

#unset_tried!Object


227
228
229
# File 'lib/opto/option.rb', line 227

def unset_tried!
  @tried_resolve = false
end

#valid?Boolean

True if value is valid

Returns:

  • (Boolean)

274
275
276
277
# File 'lib/opto/option.rb', line 274

def valid?
  return true if skip?
  handler.valid?(value)
end

#validateObject

Run validators

Raises:

  • (TypeError, ArgumentError)

180
181
182
183
184
# File 'lib/opto/option.rb', line 180

def validate
  handler.validate(@value)
rescue StandardError => ex
  raise ex, "Validation for #{name} : #{ex.message}"
end

#valueObject

The value of this option. Will try to run resolvers.

Returns:

  • option_value


196
197
198
199
200
201
# File 'lib/opto/option.rb', line 196

def value
  return @value unless @value.nil?
  return nil if skip?
  set(resolve)
  @value
end

#value_of(option_name) ⇒ Object

Get a value of another Opto::Group member

Parameters:

  • option_name (String)

172
173
174
175
176
# 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