lib/scalyr/common/util.rb in logstash-output-scalyr-0.1.19.beta vs lib/scalyr/common/util.rb in logstash-output-scalyr-0.1.20.beta

- old
+ new

@@ -1,53 +1,97 @@ module Scalyr; module Common; module Util; +class MaxKeyCountError < StandardError + attr_reader :message, :sample_keys + def initialize(message, sample_keys) + @message = message + @sample_keys = sample_keys + end +end + # Flattens a hash or array, returning a hash where keys are a delimiter-separated string concatenation of all # nested keys. Returned keys are always strings. If a non-hash or array is provided, raises TypeError. # Please see rspec util_spec.rb for expected behavior. # Includes a known bug where defined delimiter will not be used for nesting levels past the first, this is kept # because some queries and dashboards already rely on the broken functionality. -def self.flatten(obj, delimiter='_', flatten_arrays=true, fix_deep_flattening_delimiters=false) +def self.flatten(hash_obj, delimiter='_', flatten_arrays=true, fix_deep_flattening_delimiters=false, max_key_count=-1) # base case is input object is not enumerable, in which case simply return it - if !obj.respond_to?(:each) + if !hash_obj.respond_to?(:each) raise TypeError.new('Input must be a hash or array') end + # case where we pass in a valid array, but don't want to flatten arrays + if !hash_obj.respond_to?(:has_key?) and !flatten_arrays + return hash_obj + end + stack = [] + stack << hash_obj + key_stack = [] + key_stack << "" + key_list = [] + key_list_width = [] result = Hash.new - # require 'pry' - # binding.pry + test_key = 0 + #Debugging + #require 'pry' + #binding.pry - if obj.respond_to?(:has_key?) + until stack.empty? + obj = stack.pop + key_list << key_stack.pop - # input object is a hash - obj.each do |key, value| - if (flatten_arrays and value.respond_to?(:each)) or value.respond_to?(:has_key?) - flatten(value, fix_deep_flattening_delimiters ? delimiter : '_', flatten_arrays).each do |subkey, subvalue| - result["#{key}#{delimiter}#{subkey}"] = subvalue - end - else - result["#{key}"] = value + # Case when object is a hash + if obj.respond_to?(:has_key?) + key_list_width << obj.keys.count + obj.each do |key, value| + key_stack << key + stack << value end - end - elsif flatten_arrays + # Case when object is an array we intend to flatten + elsif flatten_arrays and obj.respond_to?(:each) + key_list_width << obj.count + obj.each_with_index do |value, index| + key_stack << index + stack << value + end - # input object is an array or set - obj.each_with_index do |value, index| - if value.respond_to?(:each) - flatten(value, fix_deep_flattening_delimiters ? delimiter : '_', flatten_arrays).each do |subkey, subvalue| - result["#{index}#{delimiter}#{subkey}"] = subvalue + else + result_key = "" + delim = delimiter + key_list.each_with_index do |key, index| + # We have a blank key at the start of the key list to avoid issues with calling pop, so we ignore delimiter + # for the first two keys + if index > 1 + result_key += "#{delim}#{key}" + if not fix_deep_flattening_delimiters + delim = "_" + end + else + result_key += "#{key}" end - else - result["#{index}"] = value end - end + result[result_key] = obj - else + if max_key_count > -1 and result.keys.count > max_key_count + raise MaxKeyCountError.new( + "Resulting flattened object will contain more keys than the configured flattening_max_key_count of #{max_key_count}", + result.keys[0..6] + ) + end - result = obj + throw_away = key_list.pop + until key_list_width.empty? or key_list_width[-1] > 1 + throw_away = key_list_width.pop + throw_away = key_list.pop + end + if not key_list_width.empty? + key_list_width[-1] -= 1 + end + end end return result end