lib/dottie.rb in dottie-0.0.1 vs lib/dottie.rb in dottie-0.0.2
- old
+ new
@@ -8,21 +8,27 @@
##
# Creates a new Dottie::Freckle from a standard Ruby Hash or Array.
def self.[](obj)
- Dottie::Freckle.new(obj)
+ if obj.is_a?(Dottie::Freckle)
+ obj
+ else
+ Dottie::Freckle.new(obj)
+ end
end
##
# Gets a value from an object. Does not assume the object has been extended
# with Dottie methods.
def self.get(obj, key)
Dottie.key_parts(key).each do |k|
obj = case obj
when Hash, Array
+ # use an array index if it appears that's what was intended
+ k = k.to_i if obj.is_a?(Array) && k.to_i.to_s == k
obj[k]
else
nil
end
end
@@ -37,29 +43,39 @@
key_parts = Dottie.key_parts(key)
key_parts.each_with_index do |k, i|
# set the value if this is the last key part
if i == key_parts.size - 1
case obj
- when Hash, Array
+ when Hash
obj[k] = value
+ when Array
+ case k
+ when '-', 'prepend', '>>'
+ obj.unshift(value)
+ when '+', 'append', '<<'
+ obj << value
+ else
+ obj[k] = value
+ end
else
raise TypeError.new("expected Hash or Array but got #{obj.class.name}")
end
# otherwise, walk down the tree, creating missing nodes along the way
else
obj = case obj
when Hash, Array
# look ahead at the next key to see if an array should be created
- if key_parts[i + 1].is_a?(Integer)
+ if key_parts[i + 1].is_a?(Integer) ||
+ key_parts[i + 1] =~ /\A(-|\+|prepend|append|>>|<<)\z/
obj[k] ||= []
else
obj[k] ||= {}
end
when nil
# look at the key to see if an array should be created
case k
- when Integer
+ when Integer, '-', '+', 'prepend', 'append', '>>', '<<'
obj[k] = []
else
obj[k] = {}
end
else
@@ -112,10 +128,75 @@
raise KeyError.new(%{key not found: "#{key}"})
end
end
##
+ # Deletes the value at the specified key and returns it.
+
+ def self.delete(obj, key)
+ if Dottie.has_key?(obj, key)
+ key_parts = Dottie.key_parts(key)
+ if key_parts.size > 1
+ key = Dottie.build_key(key_parts[0..-2])
+ obj = Dottie.get(obj, key)
+ end
+ if obj.is_a?(Array) && key_parts.last.is_a?(Fixnum)
+ obj.delete_at(key_parts.last)
+ else
+ obj.delete(key_parts.last)
+ end
+ else
+ nil
+ end
+ end
+
+ ##
+ # Flattens a Hash or Array to a single-depth Hash with Dottie-style keys.
+
+ def self.flatten(obj, options = {}, path = nil, flat = nil)
+ path ||= []
+ flat ||= {}
+ case obj
+ when Hash
+ obj.each do |k, v|
+ this_path = path + [k]
+ case v
+ when Hash, Array
+ flat[this_path.join('.')] = options[:keys_only] ? nil : v if options[:intermediate]
+ Dottie.flatten(v, options, this_path, flat)
+ else
+ flat[this_path.join('.')] = options[:keys_only] ? nil : v
+ end
+ end
+ when Array
+ obj.each_with_index do |v, i|
+ this_path = path.dup
+ if this_path.any?
+ this_path[-1] = this_path[-1].to_s + "[#{i}]"
+ else
+ this_path = ["[#{i}]"]
+ end
+ case v
+ when Hash, Array
+ flat[this_path.join('.')] = options[:keys_only] ? nil : v if options[:intermediate]
+ Dottie.flatten(v, options, this_path, flat)
+ else
+ flat[this_path.join('.')] = options[:keys_only] ? nil : v
+ end
+ end
+ end
+ flat
+ end
+
+ ##
+ # Gets an array of the Dottie-style keys that exist in a Hash or Array.
+
+ def self.keys(obj, options = {})
+ Dottie.flatten(obj, { keys_only: true }.merge(options)).keys
+ end
+
+ ##
# Checks whether a key looks like a key Dottie understands.
def self.dottie_key?(key)
!!(key.is_a?(String) && key =~ /[.\[]/) || key.is_a?(Array)
end
@@ -146,8 +227,25 @@
elsif key.is_a?(Array)
key
else
raise TypeError.new("expected String or Array but got #{key.class.name}")
end
+ end
+
+ ##
+ # Builds a Dottie key from an Array of strings and integers.
+
+ def self.build_key(parts)
+ key = ''
+ parts.each_with_index do |part, i|
+ case part
+ when String
+ key << '.' unless i == 0
+ key << part
+ when Fixnum
+ key << "[#{part}]"
+ end
+ end
+ key
end
end