Class: TableTennis::Util::MagicOptions
- Inherits:
-
Object
- Object
- TableTennis::Util::MagicOptions
- Defined in:
- lib/table_tennis/util/magic_options.rb
Direct Known Subclasses
Constant Summary collapse
- MAGIC_ALIASES =
{ boolean: :bool, booleans: :bools, bool: :bool, bools: :bools, float: Float, floats: :floats, int: Integer, integer: Integer, integers: :ints, ints: :ints, lambda: Proc, num: Numeric, number: Numeric, numbers: :nums, nums: :nums, proc: Proc, str: String, string: String, strings: :strs, strs: :strs, sym: Symbol, symbol: Symbol, symbols: :syms, syms: :syms, }
- MAGIC_PRETTY =
{ :bool => "boolean", Float => "float", Integer => "integer", Numeric => "number", String => "string", Symbol => "symbol", }
Instance Attribute Summary collapse
-
#magic_attributes ⇒ Object
public api (also see [] and []=).
-
#magic_values ⇒ Object
public api (also see [] and []=).
Class Method Summary collapse
-
.magic_coerce(value, type) ⇒ Object
coerce value into type.
-
.magic_is_a?(value, klass) ⇒ Boolean
like is_a?, but supports :bool and allows ints to be floats.
-
.magic_pretty(klass) ⇒ Object
pretty print a class (or :bool).
-
.magic_resolve(type) ⇒ Object
resolve :boolean to :bool, :int => Integer class, etc.
-
.magic_validate!(name, value, type) ⇒ Object
validate name=value against type, raise on failure.
-
.validate_any_of(value, possibilities) ⇒ Object
validators.
- .validate_array(value, array_type) ⇒ Object
- .validate_class(value, klass) ⇒ Object
- .validate_hash(value, hash_type) ⇒ Object
- .validate_range(value, range) ⇒ Object
- .validate_regexp(value, regexp) ⇒ Object
Instance Method Summary collapse
-
#initialize(schema, options = {}) {|_self| ... } ⇒ MagicOptions
constructor
A new instance of MagicOptions.
- #inspect ⇒ Object
-
#magic_add_attribute(name, type) ⇒ Object
magic_add_attribute.
-
#magic_get(name) ⇒ Object
(also: #[])
magic_get/set.
-
#magic_sanity(name, type) ⇒ Object
sanity check a name/type from the schema.
- #magic_set(name, value) ⇒ Object (also: #[]=)
- #to_h ⇒ Object
- #update!(hash) ⇒ Object
Constructor Details
#initialize(schema, options = {}) {|_self| ... } ⇒ MagicOptions
Returns a new instance of MagicOptions.
56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/table_tennis/util/magic_options.rb', line 56 def initialize(schema, = {}, &block) @magic_attributes, @magic_values = {}, {} if self.class == MagicOptions # rubocop:disable Style/ClassEqualityComparison raise ArgumentError, "MagicOptions is an abstract class" end schema.each { magic_add_attribute(_1, _2) } update!() if yield self if block_given? end |
Instance Attribute Details
#magic_attributes ⇒ Object
public api (also see [] and []=)
54 55 56 |
# File 'lib/table_tennis/util/magic_options.rb', line 54 def magic_attributes @magic_attributes end |
#magic_values ⇒ Object
public api (also see [] and []=)
54 55 56 |
# File 'lib/table_tennis/util/magic_options.rb', line 54 def magic_values @magic_values end |
Class Method Details
.magic_coerce(value, type) ⇒ Object
coerce value into type. pretty conservative at the moment
221 222 223 224 225 226 227 228 229 |
# File 'lib/table_tennis/util/magic_options.rb', line 221 def self.magic_coerce(value, type) if type == :bool case value when true, 1, "1", "true" then value = true when false, 0, "", "0", "false" then value = false end end value end |
.magic_is_a?(value, klass) ⇒ Boolean
like is_a?, but supports :bool and allows ints to be floats
232 233 234 235 236 237 238 239 240 |
# File 'lib/table_tennis/util/magic_options.rb', line 232 def self.magic_is_a?(value, klass) if klass == :bool value == true || value == false elsif klass == Float value.is_a?(klass) || value.is_a?(Integer) else value.is_a?(klass) end end |
.magic_pretty(klass) ⇒ Object
pretty print a class (or :bool)
279 |
# File 'lib/table_tennis/util/magic_options.rb', line 279 def self.magic_pretty(klass) = MAGIC_PRETTY[klass] || klass.to_s |
.magic_resolve(type) ⇒ Object
resolve :boolean to :bool, :int => Integer class, etc.
282 |
# File 'lib/table_tennis/util/magic_options.rb', line 282 def self.magic_resolve(type) = MAGIC_ALIASES[type] || type |
.magic_validate!(name, value, type) ⇒ Object
validate name=value against type, raise on failure
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/table_tennis/util/magic_options.rb', line 151 def self.magic_validate!(name, value, type) # we validate against coerced values, but squirrel away the original # uncoerced in case we need to use it inside an error message original, value = value, magic_coerce(value, type) return if value.nil? case type when Array then validate_any_of(value, type) when Class, :bool then validate_class(value, type) when Hash then validate_hash(value, type) when Proc then type.call(value) when Range then validate_range(value, type) when Regexp then validate_regexp(value, type) when :bools, :floats, :ints, :nums, :strs, :syms then validate_array(value, type) else raise "impossible" end value rescue ArgumentError => ex # add context to msg if necessary msg = ex. if !msg.include?("#{name} = #{original.inspect}") msg = "#{self}.#{name} = #{original.inspect} failed, #{msg}" end raise ArgumentError, msg end |
.validate_any_of(value, possibilities) ⇒ Object
validators
182 183 184 185 186 |
# File 'lib/table_tennis/util/magic_options.rb', line 182 def self.validate_any_of(value, possibilities) if !possibilities.include?(value) raise ArgumentError, "expected one of #{possibilities.inspect}" end end |
.validate_array(value, array_type) ⇒ Object
213 214 215 216 217 218 |
# File 'lib/table_tennis/util/magic_options.rb', line 213 def self.validate_array(value, array_type) klass = magic_resolve(array_type.to_s[..-2].to_sym) if !(value.is_a?(Array) && value.all? { magic_is_a?(_1, klass) }) raise ArgumentError, "expected array of #{array_type}" end end |
.validate_class(value, klass) ⇒ Object
188 189 190 191 192 |
# File 'lib/table_tennis/util/magic_options.rb', line 188 def self.validate_class(value, klass) if !magic_is_a?(value, klass) raise ArgumentError, "expected #{magic_pretty(klass)}" end end |
.validate_hash(value, hash_type) ⇒ Object
194 195 196 197 198 199 |
# File 'lib/table_tennis/util/magic_options.rb', line 194 def self.validate_hash(value, hash_type) kk, vk = hash_type.first if !(value.is_a?(Hash) && value.all? { magic_is_a?(_1, kk) && magic_is_a?(_2, vk) }) raise ArgumentError, "expected hash of #{magic_pretty(kk)} => #{magic_pretty(vk)}" end end |
.validate_range(value, range) ⇒ Object
201 202 203 204 205 |
# File 'lib/table_tennis/util/magic_options.rb', line 201 def self.validate_range(value, range) if !value.is_a?(Numeric) || !range.include?(value) raise ArgumentError, "expected to be in range #{range.inspect}" end end |
.validate_regexp(value, regexp) ⇒ Object
207 208 209 210 211 |
# File 'lib/table_tennis/util/magic_options.rb', line 207 def self.validate_regexp(value, regexp) if !value.is_a?(String) || !value.match?(regexp) raise ArgumentError, "expected to be a string matching #{regexp}" end end |
Instance Method Details
#inspect ⇒ Object
71 72 73 74 |
# File 'lib/table_tennis/util/magic_options.rb', line 71 def inspect values = magic_values.compact.map { "#{_1}=#{_2.inspect}" }.join(", ") "#<#{self.class} #{values}>" end |
#magic_add_attribute(name, type) ⇒ Object
magic_add_attribute
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/table_tennis/util/magic_options.rb', line 80 def magic_add_attribute(name, type) # resolve :boolean to :bool, :int => Integer class, etc. type = if type.is_a?(Hash) type.to_h { [self.class.magic_resolve(_1), self.class.magic_resolve(_2)] } else self.class.magic_resolve(type) end # sanity check for schema errors if (error = magic_sanity(name, type)) raise ArgumentError, "MagicOptions schema #{name.inspect} #{error}" end # all is well magic_attributes[name] = type if !respond_to?(name) define_singleton_method(name) { self[name] } end if type == :bool && !respond_to?("#{name}?") define_singleton_method("#{name}?") { !!self[name] } end if !respond_to?("#{name}=") define_singleton_method("#{name}=") { |value| self[name] = value } end end |
#magic_get(name) ⇒ Object Also known as: []
magic_get/set
132 133 134 135 |
# File 'lib/table_tennis/util/magic_options.rb', line 132 def magic_get(name) raise ArgumentError, "unknown #{self.class}.#{name}" if !magic_attributes.key?(name) magic_values[name] end |
#magic_sanity(name, type) ⇒ Object
sanity check a name/type from the schema
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/table_tennis/util/magic_options.rb', line 107 def magic_sanity(name, type) if !name.is_a?(Symbol) return "attribute names must be symbols" end if !name.to_s.match?(/\A[a-z_][0-9a-z_]*\z/i) return "attribute names must be valid method names" end case type when :bool, :bools, :floats, :ints, :nums, :strs, :syms, Class, Proc, Range, Regexp return when Array "must be an array of possible values" if type.empty? when Hash valid = type.length == 1 && type.first.all? { _1 == :bool || _1.is_a?(Class) } "must be { class => class }" if !valid else "unknown schema type #{type.inspect}" end end |
#magic_set(name, value) ⇒ Object Also known as: []=
137 138 139 140 |
# File 'lib/table_tennis/util/magic_options.rb', line 137 def magic_set(name, value) raise ArgumentError, "unknown #{self.class}.#{name}=" if !magic_attributes.key?(name) magic_values[name] = self.class.magic_validate!(name, value, magic_attributes[name]) end |
#to_h ⇒ Object
69 |
# File 'lib/table_tennis/util/magic_options.rb', line 69 def to_h = magic_values.dup |
#update!(hash) ⇒ Object
68 |
# File 'lib/table_tennis/util/magic_options.rb', line 68 def update!(hash) = hash.each { send("#{_1}=", _2) } |