opto 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +407 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/opto.rb +51 -0
- data/lib/opto/extensions/hash_string_or_symbol_key.rb +18 -0
- data/lib/opto/extensions/snake_case.rb +22 -0
- data/lib/opto/group.rb +112 -0
- data/lib/opto/option.rb +298 -0
- data/lib/opto/resolver.rb +65 -0
- data/lib/opto/resolvers/default.rb +10 -0
- data/lib/opto/resolvers/environment_variable.rb +25 -0
- data/lib/opto/resolvers/file_content.rb +32 -0
- data/lib/opto/resolvers/random_number.rb +35 -0
- data/lib/opto/resolvers/random_string.rb +84 -0
- data/lib/opto/resolvers/random_uuid.rb +15 -0
- data/lib/opto/setter.rb +66 -0
- data/lib/opto/setters/environment_variable.rb +40 -0
- data/lib/opto/type.rb +148 -0
- data/lib/opto/types/boolean.rb +57 -0
- data/lib/opto/types/enum.rb +107 -0
- data/lib/opto/types/integer.rb +47 -0
- data/lib/opto/types/string.rb +71 -0
- data/lib/opto/types/uri.rb +36 -0
- data/lib/opto/version.rb +3 -0
- data/opto.gemspec +24 -0
- metadata +119 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "opto"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/opto.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "opto/version"
|
2
|
+
require 'opto/extensions/snake_case'
|
3
|
+
require 'opto/extensions/hash_string_or_symbol_key'
|
4
|
+
|
5
|
+
if RUBY_VERSION < '2.1'
|
6
|
+
using Opto::Extension::SnakeCase
|
7
|
+
using Opto::Extension::HashStringOrSymbolKey
|
8
|
+
end
|
9
|
+
|
10
|
+
require "opto/option"
|
11
|
+
require "opto/group"
|
12
|
+
|
13
|
+
require 'yaml'
|
14
|
+
|
15
|
+
# An option parser/validator/resolver
|
16
|
+
#
|
17
|
+
module Opto
|
18
|
+
# Initialize a new Opto::Option (when input is hash) or an Opto::Group (when input is an array of hashes)
|
19
|
+
def self.new(opts)
|
20
|
+
case opts
|
21
|
+
when Hash
|
22
|
+
if opts.has_key?('name') || opts.has_key?(:name)
|
23
|
+
Option.new(opts)
|
24
|
+
else
|
25
|
+
Group.new(opts)
|
26
|
+
end
|
27
|
+
when Array
|
28
|
+
if opts.all? {|o| o.kind_of?(Hash) }
|
29
|
+
Group.new(opts)
|
30
|
+
else
|
31
|
+
raise TypeError, "Invalid input, an option hash or an array of option hashes required"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise TypeError, "Invalid input, an option hash or an array of option hashes required"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read an option (or option group) from a YAML file
|
39
|
+
# @param [String] path_to_file
|
40
|
+
# @param [String,Symbol] a key in the hash representation of the file, such as :variables to read the options from (instead of using the root)
|
41
|
+
# @example
|
42
|
+
# Opto.read('/tmp/foo.yml', :options)
|
43
|
+
def self.read(yaml_path, key=nil)
|
44
|
+
opts = YAML.load(File.read(yaml_path))
|
45
|
+
new(key.nil? ? opts : opts[key])
|
46
|
+
end
|
47
|
+
|
48
|
+
singleton_class.send(:alias_method, :load, :read)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Opto
|
2
|
+
module Extension
|
3
|
+
# Refines Hash so that [] and delete work with :symbol or 'string' keys
|
4
|
+
module HashStringOrSymbolKey
|
5
|
+
refine Hash do
|
6
|
+
def [](key)
|
7
|
+
return nil if key.nil?
|
8
|
+
super(key.to_s) || super(key.to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete(key)
|
12
|
+
return nil if key.nil?
|
13
|
+
super(key) || super(key.to_s) || super(key.to_sym)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Opto
|
2
|
+
module Extension
|
3
|
+
# Refines String to have .snakecase method that turns
|
4
|
+
# StringLikeThis into a string_like_this
|
5
|
+
module SnakeCase
|
6
|
+
refine String do
|
7
|
+
def snakecase
|
8
|
+
gsub(/::/, '/')
|
9
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
10
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
11
|
+
tr('-', '_').
|
12
|
+
gsub(/\s/, '_').
|
13
|
+
gsub(/__+/, '_').
|
14
|
+
downcase
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :underscore, :snakecase
|
18
|
+
alias_method :snakeize, :snakecase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/opto/group.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'opto/extensions/snake_case'
|
2
|
+
require 'opto/extensions/hash_string_or_symbol_key'
|
3
|
+
|
4
|
+
if RUBY_VERSION < '2.1'
|
5
|
+
using Opto::Extension::SnakeCase
|
6
|
+
using Opto::Extension::HashStringOrSymbolKey
|
7
|
+
end
|
8
|
+
|
9
|
+
module Opto
|
10
|
+
# A group of Opto::Option instances. Members of Groups can see their relatives
|
11
|
+
# and their values. Such as `option.value_of('another_option')`
|
12
|
+
#
|
13
|
+
# Most Array instance methods are delegated, such as .map, .each, .find etc.
|
14
|
+
class Group
|
15
|
+
|
16
|
+
using Opto::Extension::HashStringOrSymbolKey unless RUBY_VERSION < '2.1'
|
17
|
+
|
18
|
+
attr_reader :options, :defaults
|
19
|
+
|
20
|
+
extend Forwardable
|
21
|
+
|
22
|
+
# Initialize a new Option Group. You can also pass in :defaults.
|
23
|
+
#
|
24
|
+
# @param [Array<Hash,Opto::Option>,Hash,NilClass] opts An array of Option definition hashes or Option objects or a hash like { var_name: { opts } }.
|
25
|
+
# @return [Opto::Group]
|
26
|
+
def initialize(*options)
|
27
|
+
if options.size > 0
|
28
|
+
if options.last.kind_of?(Hash) && options.last[:defaults]
|
29
|
+
@defaults = options.pop[:defaults]
|
30
|
+
end
|
31
|
+
@options =
|
32
|
+
case options.first
|
33
|
+
when NilClass
|
34
|
+
[]
|
35
|
+
when Hash
|
36
|
+
options.first.map {|k,v| Option.new({name: k.to_s, group: self}.merge(v))}
|
37
|
+
when Array
|
38
|
+
options.first.map {|opt| opt.kind_of?(Opto::Option) ? opt : Option.new(opt.merge(group: self)) }
|
39
|
+
else
|
40
|
+
raise TypeError, "Invalid type #{options.first.class} for Opto::Group.new"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
@options = []
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Are all options valid? (Option value passes validation)
|
49
|
+
# @return [Boolean]
|
50
|
+
def valid?
|
51
|
+
options.all? {|o| o.valid? }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Collect validation errors from members
|
55
|
+
# @return [Hash] { option_name => { validator_name => "Too short" } }
|
56
|
+
def errors
|
57
|
+
Hash[*options_with_errors.flat_map {|o| [o.name, o.errors] }]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Enumerate over all the options that are not valid
|
61
|
+
# @return [Array]
|
62
|
+
def options_with_errors
|
63
|
+
options.reject(&:valid?)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Convert Group to an Array of Hashes (by calling .to_h on each member)
|
67
|
+
# @return [Array<Hash>]
|
68
|
+
def to_a(with_errors: false, with_value: false)
|
69
|
+
options.map {|opt| opt.to_h(with_errors: with_errors, with_value: with_value) }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert a Group to a hash that has { option_name => option_value }
|
73
|
+
# @return [Hash]
|
74
|
+
def to_h(values_only: false, with_values: false, with_errors: false)
|
75
|
+
if values_only
|
76
|
+
Hash[*options.flat_map {|opt| [opt.name, opt.value]}]
|
77
|
+
else
|
78
|
+
Hash[*options.flat_map {|opt| [opt.name, opt.to_h(with_value: with_values, with_errors: with_errors).reject {|k,_| k==:name}]}]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Runs outputters for all valid non-skipped options
|
83
|
+
def run
|
84
|
+
options.reject(&:skip?).select(&:valid?).each(&:output)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Initialize a new Option to this group. Takes the same arguments as Opto::Option
|
88
|
+
# @param [Hash] option_definition
|
89
|
+
# @return [Opto::Option]
|
90
|
+
def build_option(args={})
|
91
|
+
options << Option.new(args.merge(group: self))
|
92
|
+
options.last
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find a member by name
|
96
|
+
# @param [String] option_name
|
97
|
+
# @return [Opto::Option]
|
98
|
+
def option(option_name)
|
99
|
+
options.find { |opt| opt.name == option_name }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get a value of a member by option name
|
103
|
+
# @param [String] option_name
|
104
|
+
# @return [option_value, NilClass]
|
105
|
+
def value_of(option_name)
|
106
|
+
opt = option(option_name)
|
107
|
+
opt.nil? ? nil : opt.value
|
108
|
+
end
|
109
|
+
|
110
|
+
def_delegators :@options, *(Array.instance_methods - [:__send__, :object_id, :to_h, :to_a, :is_a?, :kind_of?, :instance_of?])
|
111
|
+
end
|
112
|
+
end
|
data/lib/opto/option.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
require_relative 'type'
|
2
|
+
require_relative 'resolver'
|
3
|
+
require_relative 'setter'
|
4
|
+
require_relative 'extensions/snake_case'
|
5
|
+
require_relative 'extensions/hash_string_or_symbol_key'
|
6
|
+
|
7
|
+
if RUBY_VERSION < '2.1'
|
8
|
+
using Opto::Extension::SnakeCase
|
9
|
+
using Opto::Extension::HashStringOrSymbolKey
|
10
|
+
end
|
11
|
+
|
12
|
+
module Opto
|
13
|
+
# What is an option? It's like a variable that has a value, which can be validated or
|
14
|
+
# manipulated on creation. The value can be resolved from a number of origins, such as
|
15
|
+
# an environment variable or random string generator.
|
16
|
+
class Option
|
17
|
+
|
18
|
+
unless RUBY_VERSION < '2.1'
|
19
|
+
using Opto::Extension::SnakeCase
|
20
|
+
using Opto::Extension::HashStringOrSymbolKey
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :type
|
24
|
+
attr_accessor :name
|
25
|
+
attr_accessor :label
|
26
|
+
attr_accessor :description
|
27
|
+
attr_accessor :required
|
28
|
+
attr_accessor :default
|
29
|
+
attr_reader :from
|
30
|
+
attr_reader :to
|
31
|
+
attr_reader :group
|
32
|
+
attr_reader :skip_if
|
33
|
+
attr_reader :only_if
|
34
|
+
attr_reader :initial_value
|
35
|
+
attr_reader :type_options
|
36
|
+
|
37
|
+
# Initialize an instance of Opto::Option
|
38
|
+
# @param [Hash] options
|
39
|
+
# @option [String] :name Option name
|
40
|
+
# @option [String,Symbol] :type Option type, such as :integer, :string, :boolean, :enum
|
41
|
+
# @option [String] :label A label for this field, to be used in for example an interactive prompt
|
42
|
+
# @option [String] :description Same as label, but more detailed
|
43
|
+
# @option [*] :default Default value for option
|
44
|
+
# @option [String,Symbol,Array<String,Symbol,Hash>,Hash] :from Resolver origins
|
45
|
+
# @option [String,Symbol,Array<String,Symbol,Hash>,Hash] :to Setter targets
|
46
|
+
# @option [String,Symbol,Array<String,Symbol,Hash>,Hash] :skip_if Conditionals that define if this option should be skipped
|
47
|
+
# @option [String,Symbol,Array<String,Symbol,Hash>,Hash] :only_if Conditionals that define if this option should be included
|
48
|
+
# @option [Opto::Group] :group Parent group reference
|
49
|
+
# @option [...] Type definition options, such as { min_length: 3, strip: true }
|
50
|
+
#
|
51
|
+
# @example Create an option
|
52
|
+
# Opto::Option.new(
|
53
|
+
# name: 'cat_name',
|
54
|
+
# type: 'string',
|
55
|
+
# label: 'Name of your Cat',
|
56
|
+
# required: true,
|
57
|
+
# description: 'Enter a name for your cat',
|
58
|
+
# from:
|
59
|
+
# env: 'CAT_NAME'
|
60
|
+
# only_if:
|
61
|
+
# pet: 'cat'
|
62
|
+
# min_length: 2
|
63
|
+
# max_length: 20
|
64
|
+
# )
|
65
|
+
#
|
66
|
+
# @example Create a random string
|
67
|
+
# Opto::Option.new(
|
68
|
+
# name: 'random_string',
|
69
|
+
# type: :string,
|
70
|
+
# from:
|
71
|
+
# random_string:
|
72
|
+
# length: 20
|
73
|
+
# charset: ascii_printable
|
74
|
+
# )
|
75
|
+
def initialize(options = {})
|
76
|
+
opts = options.dup
|
77
|
+
|
78
|
+
@group = opts.delete(:group)
|
79
|
+
if @group && @group.defaults
|
80
|
+
opts = @group.defaults.reject{|k,_| [:from, :to].include?(k)}.merge(opts)
|
81
|
+
end
|
82
|
+
|
83
|
+
@name = opts.delete(:name).to_s
|
84
|
+
|
85
|
+
type = opts.delete(:type)
|
86
|
+
@type = type.to_s.snakecase unless type.nil?
|
87
|
+
|
88
|
+
@label = opts.delete(:label) || @name
|
89
|
+
@description = opts.delete(:description)
|
90
|
+
@default = opts.delete(:default)
|
91
|
+
val = opts.delete(:value)
|
92
|
+
@skip_if = opts.delete(:skip_if)
|
93
|
+
@only_if = opts.delete(:only_if)
|
94
|
+
@skip_lambdas = normalize_ifs(@skip_if)
|
95
|
+
@only_lambdas = normalize_ifs(@only_if)
|
96
|
+
@from = { default: self }.merge(normalize_from_to(opts.delete(:from)))
|
97
|
+
@to = normalize_from_to(opts.delete(:to))
|
98
|
+
@type_options = opts
|
99
|
+
|
100
|
+
set_initial(val) if val
|
101
|
+
deep_merge_defaults
|
102
|
+
end
|
103
|
+
|
104
|
+
def deep_merge_defaults
|
105
|
+
return nil unless group && group.defaults
|
106
|
+
if group.defaults[:from]
|
107
|
+
normalize_from_to(group.defaults[:from]).each do |k,v|
|
108
|
+
from[k] ||= v
|
109
|
+
end
|
110
|
+
end
|
111
|
+
if group.defaults[:to]
|
112
|
+
normalize_from_to(group.defaults[:to]).each do |k,v|
|
113
|
+
to[k] ||= v
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Hash representation of Opto::Option. Can be passed back to Opto::Option.new
|
119
|
+
# @param [Boolean] with_errors Include possible validation errors hash
|
120
|
+
# @param [Boolean] with_value Include current value
|
121
|
+
# @return [Hash]
|
122
|
+
def to_h(with_errors: false, with_value: true)
|
123
|
+
hash = {
|
124
|
+
name: name,
|
125
|
+
label: label,
|
126
|
+
type: type,
|
127
|
+
description: description,
|
128
|
+
default: default,
|
129
|
+
from: from.reject { |k,_| k == :default},
|
130
|
+
to: to
|
131
|
+
}.merge(type_options).reject { |_,v| v.nil? }
|
132
|
+
hash[:skip_if] = skip_if if skip_if
|
133
|
+
hash[:only_if] = only_if if only_if
|
134
|
+
hash[:errors] = errors if with_errors
|
135
|
+
hash[:value] = value if with_value
|
136
|
+
hash
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set option value. Also aliased as #value=
|
140
|
+
# @param value
|
141
|
+
def set(value)
|
142
|
+
@value = handler.sanitize(value)
|
143
|
+
validate
|
144
|
+
@value
|
145
|
+
end
|
146
|
+
|
147
|
+
alias_method :value=, :set
|
148
|
+
|
149
|
+
# Returns true if this field should not be processed because of the conditionals
|
150
|
+
# @return [Boolean]
|
151
|
+
def skip?
|
152
|
+
return true if @skip_lambdas.any? { |s| s.call(self) }
|
153
|
+
return true unless @only_lambdas.all? { |s| s.call(self) }
|
154
|
+
false
|
155
|
+
end
|
156
|
+
|
157
|
+
# Get a value of another Opto::Group member
|
158
|
+
# @param [String] option_name
|
159
|
+
def value_of(option_name)
|
160
|
+
group.nil? ? nil : group.value_of(option_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Run validators
|
164
|
+
# @raise [TypeError, ArgumentError]
|
165
|
+
def validate
|
166
|
+
handler.validate(@value)
|
167
|
+
rescue StandardError => ex
|
168
|
+
raise ex, "Validation for #{name} : #{ex.message}"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Access the Opto::Type handler for this option
|
172
|
+
# @return [Opto::Type]
|
173
|
+
def handler
|
174
|
+
@handler ||= Type.for(type).new(type_options)
|
175
|
+
end
|
176
|
+
|
177
|
+
# The value of this option. Will try to run resolvers.
|
178
|
+
# @return option_value
|
179
|
+
def value
|
180
|
+
return @value unless @value.nil?
|
181
|
+
return nil if skip?
|
182
|
+
set(resolve)
|
183
|
+
@value
|
184
|
+
end
|
185
|
+
|
186
|
+
# Accessor to defined resolvers for this option.
|
187
|
+
# @return [Array<Opto::Resolver>]
|
188
|
+
def resolvers
|
189
|
+
@resolvers ||= from.map { |origin, hint| Resolver.for(origin).new(hint, self) }
|
190
|
+
end
|
191
|
+
|
192
|
+
def setters
|
193
|
+
@setters ||= to.map { |target, hint| Setter.for(target).new(hint, self) }
|
194
|
+
end
|
195
|
+
|
196
|
+
# True if this field is defined as required: true
|
197
|
+
# @return [Boolean]
|
198
|
+
def required?
|
199
|
+
handler.required?
|
200
|
+
end
|
201
|
+
|
202
|
+
# Run resolvers
|
203
|
+
# @raise [TypeError, ArgumentError]
|
204
|
+
def resolve
|
205
|
+
resolvers.each do |resolver|
|
206
|
+
begin
|
207
|
+
resolver.respond_to?(:before) && resolver.before(self)
|
208
|
+
result = resolver.resolve
|
209
|
+
resolver.respond_to?(:after) && resolver.after(self)
|
210
|
+
rescue StandardError => ex
|
211
|
+
raise ex, "Resolver '#{resolver.origin}' for '#{name}' : #{ex.message}"
|
212
|
+
end
|
213
|
+
if result
|
214
|
+
@origin = resolver.origin
|
215
|
+
return result
|
216
|
+
end
|
217
|
+
end
|
218
|
+
nil
|
219
|
+
end
|
220
|
+
|
221
|
+
# Run setters
|
222
|
+
def output
|
223
|
+
setters.each do |setter|
|
224
|
+
begin
|
225
|
+
setter.respond_to?(:before) && setter.before(self)
|
226
|
+
setter.set(value)
|
227
|
+
setter.respond_to?(:after) && setter.after(self)
|
228
|
+
rescue StandardError => ex
|
229
|
+
raise ex, "Setter '#{setter.target}' for '#{name}' : #{ex.message}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# True if value is valid
|
235
|
+
# @return [Boolean]
|
236
|
+
def valid?
|
237
|
+
return true if skip?
|
238
|
+
handler.valid?(value)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Validation errors
|
242
|
+
# @return [Hash]
|
243
|
+
def errors
|
244
|
+
handler.errors
|
245
|
+
end
|
246
|
+
|
247
|
+
def normalize_ifs(ifs)
|
248
|
+
case ifs
|
249
|
+
when NilClass
|
250
|
+
[]
|
251
|
+
when Array
|
252
|
+
ifs.map do |iff|
|
253
|
+
lambda { |opt| !opt.value_of(iff).nil? }
|
254
|
+
end
|
255
|
+
when Hash
|
256
|
+
ifs.each_with_object([]) do |(k, v), arr|
|
257
|
+
arr << lambda { |opt| opt.value_of(k.to_s) == v }
|
258
|
+
end
|
259
|
+
when String, Symbol
|
260
|
+
[lambda { |opt| !opt.value_of(ifs.to_s).nil? }]
|
261
|
+
else
|
262
|
+
raise TypeError, "Invalid syntax for if"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def normalize_from_to(inputs)
|
267
|
+
case inputs
|
268
|
+
when Array
|
269
|
+
case inputs.first
|
270
|
+
when String, Symbol
|
271
|
+
inputs.each_with_object({}) { |o, hash| hash[o.to_s.snakecase.to_sym] = name }
|
272
|
+
when Hash
|
273
|
+
inputs.each_with_object({}) { |o, hash| o.each { |k,v| hash[k.to_s.snakecase.to_sym] = v } }
|
274
|
+
when NilClass
|
275
|
+
{}
|
276
|
+
else
|
277
|
+
raise TypeError, "Invalid format #{inputs.inspect}"
|
278
|
+
end
|
279
|
+
when Hash
|
280
|
+
inputs.each_with_object({}) { |(k, v), hash| hash[k.to_s.snakecase.to_sym] = v }
|
281
|
+
when String, Symbol
|
282
|
+
{ inputs.to_s.snakecase.to_sym => name }
|
283
|
+
when NilClass
|
284
|
+
{}
|
285
|
+
else
|
286
|
+
raise TypeError, "Invalid format #{inputs.inspect}"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
def set_initial(value)
|
293
|
+
return nil if value.nil?
|
294
|
+
@origin = :initial
|
295
|
+
set(value)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|