Skip to content

Commit

Permalink
Merge pull request #2 from ConvincibleMedia/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Convincible authored Oct 8, 2019
2 parents 26e65ca + 0b9f373 commit 92c2ca0
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 171 deletions.
5 changes: 0 additions & 5 deletions .travis.yml

This file was deleted.

248 changes: 124 additions & 124 deletions lib/key_dial/coercion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ module KeyDial

module Coercion

module Hashes

# Convert a Hash to a Struct. {a: 1, b: 2, c: 3} will become <Struct :a=1, :b=2, :c=3>
#
def to_struct(type_class = nil)
return Coercion::Structs.create(self, type_class)
module Coercer
def to(type)
if type.is_a?(Class)
if type == self.class || self.class < type
return self
elsif (type == Hash || type < Hash) && type.respond_to?(:from)
return type.from(self)
elsif (type == Array || type < Array) && type.respond_to?(:from)
return type.from(self)
elsif (type == Struct || type < Struct) && type.respond_to?(:from)
return type.from(self)
else
raise ArgumentError, "Cannot coerce to " + type.to_s
end
else
raise ArgumentError, "Must specify a class to coerce to."
end
end
end

module Hashes

# Adds .to() method to instances of this class
include Coercer

def self.included(base)
base.extend ClassMethods
Expand All @@ -19,10 +36,30 @@ module ClassMethods
# Allows you to do Hash.from(obj) to create a Hash from any object intelligently.
#
def from(obj)
return obj if obj.is_a?(Hash)
return obj.to_hash if obj.is_a?(Array)
return obj.to_h if obj.is_a?(Struct)
return {0 => obj}
case obj
when Hash
return obj
when Array
# Hash from Array. Forgiving and avoids errors. ['a', 'b', 'c'] will become {0 => 'a', 1 => 'b', 2 => 'c'}
obj.each_with_index.map { |k, i|
if k.is_a?(Array)
if k.empty?
[i, nil]
elsif k.size == 2
k # k in this case is a keyval pair, e.g. [k, v]
else
[i, k]
end
else
[i, k]
end
}.to_h
when Struct
# Hash from Struct
return obj.to_h
else
{0 => obj}
end
end

end
Expand All @@ -31,31 +68,8 @@ def from(obj)

module Arrays

# Convert an Array to a Hash, providing an alternative to the native to_h() method. to_hash() is more forgiving and avoids errors. ['a', 'b', 'c'] will become {0 => 'a', 1 => 'b', 2 => 'c'}
#
def to_hash
self.each_with_index.map { |k, i|
if k.is_a?(Array)
if k.empty?
[i, nil]
elsif k.size == 2
k # k in this case is a keyval pair, e.g. [k, v]
else
[i, k]
end
else
[i, k]
end
}.to_h
end

# Convert an Array to a Struct. ['a', 'b', 'c'] will become <Struct :'0'='a', :'1'='b', :'2'='c'>
#
# @param type_class If a sub-class of Struct is provided, this sub-class will be instantiated
#
def to_struct(type_class = nil)
return Coercion::Structs.create(self, type_class)
end
# Adds .to() method to instances of this class
include Coercer

def self.included(base)
base.extend ClassMethods
Expand All @@ -66,10 +80,18 @@ module ClassMethods
# Allows you to do Array.from(obj) to create an Array from any object intelligently.
#
def from(obj)
return obj if obj.is_a?(Array)
return obj.to_a if obj.is_a?(Hash)
return obj.to_h.to_a if obj.is_a?(Struct)
return [obj]
case obj
when Array
return obj
when Hash
# Array from Hash
return obj.to_a
when Struct
# Array from Struct
return obj.to_h.to_a
else
return [obj]
end
end

end
Expand All @@ -78,19 +100,10 @@ def from(obj)

module Structs

EMPTY = Struct.new(:'0').new.freeze
# Adds .to() method to instances of this class
include Coercer

# Convert a Struct to another Struct.
#
# @param type_class If a sub-class of Struct is provided, the Struct will be converted to this sub-class
#
def to_struct(type_class = nil)
if type_class.is_a?(Class) && type_class < Struct
return Struct.from(self, type_class)
else
return self
end
end
EMPTY = Struct.new(:'0').new.freeze

def self.included(base)
base.extend ClassMethods
Expand All @@ -100,86 +113,73 @@ module ClassMethods

# Allows you to do Struct.from(obj) to instantiate a Struct using keys/values from any object intelligently.
#
# @param type_class If a sub-class of Struct is provided, this sub-class will be instantiated
#
def from(obj, type_class = nil)
if !obj.is_a?(Struct) && !obj.is_a?(Hash) && !obj.is_a?(Array) && type_class == nil
s = EMPTY.dup
s[0] = obj
return s
else
return Coercion::Structs.create(obj, type_class)
end
end

end

# Struct creation function not really meant to be used directly. Prefer Struct.from(obj).
#
# @param from_obj Keys/values from this object will be used to fill out the new Struct.
# @param type_class If a sub-class of Struct is provided, this sub-class will be instantiated
#
def self.create(from_obj, type_class = nil)
if from_obj.is_a?(Hash) || from_obj.is_a?(Array) || from_obj.is_a?(Struct)
return EMPTY.dup if from_obj.empty? && type_class == nil
from = from_obj
else
from = [from_obj]
end

# Has a specific type of Struct been specified?
if type_class.is_a?(Class) && type_class < Struct
if from.is_a?(type_class)
# Has an instantiation of that type of Struct been passed in? If so, just return it
return from
else
values = []
if from.is_a?(Array)
# Get as many elements of array as this Struct can handle - discard the rest
values = from.first(type_class.members.size)
else
# Not an Array, so must be another Struct or Hash
type_class.members.each { |key|
if from.key?(key)
# If the object has this expected key, use it
values << from[key]
else
# Otherwise, fill this key with nil
values << nil
# Keys in the from object which don't match expected keys are discarded
end
}
end
# values now contains a value or nil for each of this class's expected keys
return type_class.new(*values)
def from(_obj)
from_obj = _obj
if !from_obj.is_a?(Struct) && !from_obj.is_a?(Hash) && !from_obj.is_a?(Array)
return EMPTY if _obj == EMPTY[0]
return EMPTY if _obj.nil?
# For non-keyed objects, we will treat the whole object like it's a value to push into the first property of a struct
from_obj = [_obj]
end
else
# Anonymous Struct
new_values = from.is_a?(Array) ? from : from.values
# Iterate over the keys of the from object
# (Array.keys is monkeypatched in)
new_keys = from.keys.each_with_index.map { |k, i|
if k.respond_to?(:to_sym) && k != ''
k.to_sym
elsif k.respond_to?(:to_s) && !k.nil?
k.to_s.to_sym

struct_class = self

# Are we operating on a defined type of Struct?
if struct_class < Struct
if from_obj.is_a?(struct_class)
# Has an instantiation of that type of Struct been passed in? If so, just return it
return from_obj
else
# If we can't construct a valid Struct key for this key, we discard the corresponding value
new_values.delete_at(i)
nil
# Get values
values = []
if from_obj.is_a?(Array)
# Get as many elements of array as this Struct can handle - discard the rest
values = from_obj.first(struct_class.members.size)
else
# Not an Array, so must be another Struct or Hash
struct_class.members.each { |key|
if from_obj.key?(key)
# If the object has this expected key, use it
values << from_obj[key]
else
# Otherwise, fill this key with nil
values << nil
# Keys in the from object which don't match expected keys are discarded
end
}
end
# values now contains a value or nil for each of this class's expected keys
return struct_class.new(*values)
end
}.reject(&:nil?)
if new_keys.size > 0
# Create anonymous Struct with the specified keys and values
return Struct.new(*new_keys).new(*new_values)
else
# Return the Empty Struct
return EMPTY.dup
# Anonymous Struct
new_values = from_obj.is_a?(Array) ? from_obj : from_obj.values
# Iterate over the keys of the from object
# (Array.keys is monkeypatched in)
new_keys = from_obj.keys.each_with_index.map { |k, i|
if k.respond_to?(:to_sym) && k != ''
k.to_sym
elsif k.respond_to?(:to_s) && !k.nil?
k.to_s.to_sym
else
# If we can't construct a valid Struct key for this key, we discard the corresponding value
new_values.delete_at(i)
nil
end
}.reject(&:nil?)
if new_keys.size > 0
# Create anonymous Struct with the specified keys and values
return Struct.new(*new_keys).new(*new_values)
else
# Return the Empty Struct
return EMPTY
end
end

rescue
return EMPTY
end

rescue
return EMPTY.dup
end

end
Expand Down
2 changes: 1 addition & 1 deletion lib/key_dial/key_dialler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def insist!(type_class = (type_class_skipped = true; nil))
# Why would you do this?
deep_obj[key[:this][:value]] = Struct.from(deep_obj[key[:this][:value]])
elsif type_class.is_a?(Class) && type_class < Struct
deep_obj[key[:this][:value]] = Struct.from(deep_obj[key[:this][:value]], type_class)
deep_obj[key[:this][:value]] = type_class.from(deep_obj[key[:this][:value]])
elsif type_class == String && deep_obj[key[:this][:value]].respond_to?(:to_s)
deep_obj[key[:this][:value]] = deep_obj[key[:this][:value]].to_s
elsif type_class == Symbol
Expand Down
2 changes: 1 addition & 1 deletion lib/key_dial/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module KeyDial
VERSION = "1.1.0"
VERSION = "1.2.0"
end
Loading

0 comments on commit 92c2ca0

Please sign in to comment.