Module: Flatten::UtilityMethods

Included in:
Flatten
Defined in:
lib/flatten/utility_methods.rb

Overview

The Utility Methods provide a significant (~4x) performance increase over extend-ing instance methods everywhere we need them.

Instance Method Summary collapse

Instance Method Details

#expand(smash_hsh, smash_key, options = {}, &block) ⇒ Object

Given a smash hash, unflatten a subset by address, returning a *modified copy* of the original smash hash.

~~~ ruby smash = => 2, ‘a.c.d’ => 4, ‘a.c.e’ => 3, ‘b.f’ => 4 Flatten::expand(smash, ‘a.c’) # => => 2, ‘a.c’ => {‘d’ => 4, ‘e’ => 3, ‘b.f’ => 4} ~~~

Parameters:

  • smash_hsh (Hash{String=>Object})
  • smash_key (String)

Returns:

  • (Object)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/flatten/utility_methods.rb', line 147

def expand(smash_hsh, smash_key, *args)
  # if smash_hsh includes our key, its value is already expanded.
  return smash_hsh if smash_hsh.include?(smash_key)

  options = (args.last.kind_of?(Hash) ? args.pop : {})
  separator = options.fetch(:separator, DEFAULT_SEPARATOR)
  pattern = /\A#{Regexp.escape(smash_key)}#{Regexp.escape(separator)}/i

  match = {}
  unmatch = {}
  smash_hsh.each do |k, v|
    if pattern =~ k
      sk = k.gsub(pattern, '')
      match[sk] = v
    else
      unmatch[k] = v
    end
  end

  unmatch.update(smash_key => unsmash(match, options)) unless match.empty?
  unmatch
end

#expand!(smash_hsh, *args) ⇒ Object

Given a smash hash, unflatten a subset by address *in place* (@see Flatten::UtilityMethods#expand)



172
173
174
# File 'lib/flatten/utility_methods.rb', line 172

def expand!(smash_hsh, *args)
  smash_hsh.replace expand(smash_hsh, *args)
end

#smash(hsh, options = {}) ⇒ Hash<String,Object>

Returns a smash version of the given hash

Parameters:

  • hsh (Hash<#to_s,Object>)

Returns:

  • (Hash<String,Object>)


47
48
49
50
51
52
# File 'lib/flatten/utility_methods.rb', line 47

def smash(hsh, options = {})
  enum = smash_each(hsh, options)
  enum.each_with_object(Hash.new) do |(key, value), memo|
    memo[key] = value
  end
end

#smash_each(hsh, options = {}) {|| ... } ⇒ void #smash_each(hsh, options = {}) ⇒ Enumerator<(smash_key,value)>

Provides a way to iterate through a deeply-nested hash as if it were a smash-hash. Used internally for generating and deconstructing smash hashes.

Overloads:

  • #smash_each(hsh, options = {}) {|| ... } ⇒ void

    This method returns an undefined value.

    Yields once per key in smash version of itself.

    Parameters:

    • hsh (Hash<#to_s,Object>)

    Yield Parameters:

    • ((smash_key,value))
  • #smash_each(hsh, options = {}) ⇒ Enumerator<(smash_key,value)>

    Parameters:

    • hsh (Hash<#to_s,Object>)

    Returns:

    • (Enumerator<(smash_key,value)>)


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/flatten/utility_methods.rb', line 22

def smash_each(hsh, options = {}, &block)
  return enum_for(:smash_each, hsh, options) unless block_given?

  inherited_prefix = options.fetch(:prefix, nil)
  separator = options.fetch(:separator, DEFAULT_SEPARATOR)
  smash_array = options.fetch(:smash_array, false)

  hsh.each do |partial_key, value|
    key = escaped_join(inherited_prefix, partial_key.to_s, separator)
    if value.kind_of?(Hash) && !value.empty?
      smash_each(value, options.merge(prefix: key), &block)
    elsif smash_array && value.kind_of?(Array) && !value.empty?
      zps = (smash_array == :zero_pad ? "%0#{value.count.to_s.size}d" : '%d')# zero-pad string
      smash_each(value.count.times.map(&zps.method(:%)).zip(value), options.merge(prefix: key), &block)
    else
      yield key, value
    end
  end
end

#smash_fetch(hsh, smash_key, default, options = {}) ⇒ Object #smash_fetch(hsh, smash_key, options = {}, &block) ⇒ Object #smash_fetch(hsh, smash_key, options = {}) ⇒ Object

Fetch a smash key from the given deeply-nested hash.

Overloads:

  • #smash_fetch(hsh, smash_key, default, options = {}) ⇒ Object

    Parameters:

    • hsh (Hash<#to_s,Object>)
    • smash_key (#to_s)
    • default (Object)

      returned if smash key not found

    Returns:

    • (Object)
  • #smash_fetch(hsh, smash_key, options = {}, &block) ⇒ Object

    Parameters:

    • hsh (Hash<#to_s,Object>)
    • smash_key (#to_s)

    Yield Returns:

    • is returned if key not found

    Returns:

    • (Object)
  • #smash_fetch(hsh, smash_key, options = {}) ⇒ Object

    Parameters:

    • hsh (Hash<#to_s,Object>)
    • smash_key (#to_s)

    Returns:

    • (Object)

    Raises:

    • KeyError if key not found



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/flatten/utility_methods.rb', line 99

def smash_fetch(hsh, smash_key, *args, &block)
  options = ( args.last.kind_of?(Hash) ? args.pop : {})
  default = args.pop

  separator = options.fetch(:separator, DEFAULT_SEPARATOR)

  escaped_split(smash_key, separator).reduce(hsh) do |memo, kp|
    if memo.kind_of?(Hash) and memo.has_key?(kp)
      memo.fetch(kp)
    elsif default
      return default
    elsif block_given?
      return yield
    else
      raise KeyError, smash_key
    end
  end
end

#smash_get(hsh, smash_key, options = {}) ⇒ Object

Get a smash key from the given deeply-nested hash, or return nil if key not found.

Worth noting is that Hash#default_proc is not used, as the intricacies of implementation would lead to all sorts of terrible surprises.

Parameters:

  • hsh (Hash<#to_s,Object>)
  • smash_key (#to_s)

Returns:

  • (Object)


128
129
130
# File 'lib/flatten/utility_methods.rb', line 128

def smash_get(hsh, smash_key, options = {})
  smash_fetch(hsh, smash_key, options) { nil }
end

#unsmash(hsh, options = {}) ⇒ Hash<String,Object>

Returns a deeply-nested version of the given smash hash

Parameters:

  • hsh (Hash<#to_s,Object>)

Returns:

  • (Hash<String,Object>)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/flatten/utility_methods.rb', line 58

def unsmash(hsh, options = {})
  separator = options.fetch(:separator, DEFAULT_SEPARATOR)
  hsh.each_with_object({}) do |(k, v), memo|
    current = memo
    key = escaped_split(k, separator)
    puts "key: #{key}"
    up_next = partial = key.shift
    until key.size.zero?
      up_next = key.shift
      up_next = up_next.to_i if (up_next =~ /\A[0-9]+\Z/)
      current = (current[partial] ||= (up_next.kind_of?(Integer) ? [] : {}))
      case up_next
      when Integer then raise KeyError unless current.kind_of?(Array)
      else              raise KeyError unless current.kind_of?(Hash)
      end
      partial = up_next
    end
    current[up_next] = v
  end
end