Module: RSpec::Puppet::Yaml::DataHelpers

Defined in:
lib/rspec-puppet-yaml/data_helpers.rb

Overview

A collection of static methods that help coerce data into simpler (to digest) forms. This is necessitated primarily because YAML files can be written with either String or Symbol keys for the same value and because Hashes can be explicitly or implicitly named.

Class Method Summary collapse

Class Method Details

.get_array_of_named_hashes(key, data = {}) ⇒ Array[Hash]

Condenses a collection of named Hashes into an Array-of-Hashes.

Accepts Hashes-of-Hashes -- where each key is the entry's :name and -- Arrays-of-Hashes -- where each Hash has a :name element -- returning both forms as an Array-of-Hashes with a guaranteed 'name' element. Any entry without a :name or 'name' generates an exception.

Parameters:

  • key (Enum[String,Symbol])

    The name of the collection to copy.

  • data (Optional[Hash]) (defaults to: {})

    The Hash to copy key from.

Returns:

  • (Array[Hash])

    Array-of-Hashes, each with a 'name' attribute.

Raises:

  • (ArgumentError)

    when an element has no name or is not a Hash.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
105
106
107
# File 'lib/rspec-puppet-yaml/data_helpers.rb', line 64

def self.get_array_of_named_hashes(key, data = {})
  coerced_hashes = []
  hashes         = RSpec::Puppet::Yaml::DataHelpers.get_named_value(
    key,
    data,
    {}
  )
  return coerced_hashes if hashes.empty?

  # Supported Cases:
  # 1. [{'this is a name' => {:key => val}}, {'this is also a name' => {:key => val}}]
  # 2. {'this is a name' => {:key => value}, 'this is also a name' => {:key => value}}
  # 3. [{:name => 'the name', :key => value}, {:name => 'another name', :key => value}]
  # 4. {:name => 'the name', :key => value}
  if hashes.kind_of?(Array)
    hashes.each { |hash|
      coerced_hashes << RSpec::Puppet::Yaml::DataHelpers
        .make_hash_name_explicit(hash)
    }
  elsif hashes.is_a?(Hash)
    # Supported Case 2 requires that each Hash attribute be a named Hash
    # when there is no explicit name attribute.
    if hashes.has_key?('name') || hashes.has_key?(:name)
      coerced_hashes << RSpec::Puppet::Yaml::DataHelpers
        .make_hash_name_explicit(hashes)
    else
      hashes.each do |k, v|
        if v.nil?
          coerced_hashes << { 'name' => k }
        elsif !v.is_a?(Hash)
          raise ArgumentError, "#{key} indicates a Hash but at least one of its attributes is neither a Hash nor nil."
        else
          coerced_hashes << v
            .select {|m, n| m != :name}
            .merge({'name' => k})
        end
      end
    end
  else
    raise ArgumentError, "#{key} is for neither an Array nor a Hash value."
  end

  coerced_hashes
end

.get_named_hash(key, data = {}, default = {}) ⇒ Hash

Gets a named Hash child from a supplied Hash parent.

Attempts to get an immediate child Hash from a parent Hash that is named according to a given key. The key may be supplied in either String or Symbol form and both are searched for. When both forms exist in the parent Hash, a shallow merge is attempted, favoring the String form.

Parameters:

  • key (Enum[String,Symbol])

    The name of the child Hash.

  • data (Optional[Hash]) (defaults to: {})

    The parent Hash.

  • default (Optional[Hash]) (defaults to: {})

    The result when key not found.

Returns:

  • (Hash)

    The selected child Hash if it exists, else default.

Raises:

  • NameError when the value for key is not a Hash.



122
123
124
125
126
127
128
129
130
# File 'lib/rspec-puppet-yaml/data_helpers.rb', line 122

def self.get_named_hash(key, data = {}, default = {})
  hash_value = RSpec::Puppet::Yaml::DataHelpers.get_named_value(
    key, data, default
  )
  if !hash_value.is_a?(Hash)
    raise NameError, "The value of #{key} is not a Hash."
  end
  hash_value
end

.get_named_value(key, data = {}, default = nil) ⇒ Any

Gets a named value from a Hash by its String and Symbol names.

Searches a Hash for a key by both its String and Symbol names. When both are found, they are merged as long as their values are both Hash (shallow) or Array (unique). An exception is raised when the values are scalar or of different data types.

Parameters:

  • key (Enum[String,Symbol])

    The name of the child.

  • data (Optional[Hash]) (defaults to: {})

    The parent Hash.

  • default (Optional[Hash]) (defaults to: nil)

    The result when key not found.

Returns:

  • (Any)

    The selected value if it exists, else default

Raises:

  • NameError when both String and Symbol forms key exist but one cannot be merged into the other.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/rspec-puppet-yaml/data_helpers.rb', line 145

def self.get_named_value(key, data = {}, default = nil)
  return default unless !data.nil? && data.respond_to?(:has_key?)
  str_key     = key.to_s
  sym_key     = key.to_sym
  has_str_key = data.has_key?(str_key)
  has_sym_key = data.has_key?(sym_key)

  if has_str_key && has_sym_key
    str_value = data[str_key]
    sym_value = data[sym_key]
    if str_value.is_a?(Hash) && sym_value.is_a?(Hash)
      begin
        sym_value.merge(str_value)
      rescue
        raise NameError, "Both symbolic and string forms of '#{str_key}' Hash declarations exist but one cannot be merged into the other.  Pick one form or the other or ensure both are Hashes that can be merged."
      end
    elsif str_value.kind_of?(Array) && sym_value.kind_of?(Array)
      str_value | sym_value
    else
      raise NameError, "Both symbolic and string forms of '#{str_key}' scalar declarations exist and they cannot be combined.  Pick one form or the other."
    end
  elsif has_str_key
    data[str_key]
  elsif has_sym_key
    data[sym_key]
  else
    default
  end
end

.make_hash_name_explicit(transform_hash = {}) ⇒ Object

Takes a Hash, checks it for a name attribute, and ensures that name is a string-named, 'name', attribute. If the Hash has no identifyable name, an ArgumentError is raised.

Parameters:

  • transform_hash (Hash) (defaults to: {})

    A Hash that may already have an explicit name attribute in either string or symbol form, or a Hash with an implicit name of form { 'implicit' => { key... => val... } }.

Returns:

  • Hash The transform_hash with an explicit, string key 'name' attribute.

Raises:

  • ArgumentError when transform_hash has neither an implicit nor explicit name.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rspec-puppet-yaml/data_helpers.rb', line 19

def self.make_hash_name_explicit(transform_hash = {})
  raise ArgumentError, "Cannot transform non-Hash data." unless transform_hash.is_a?(Hash)
  transformed_hash = {}
  hash_name        = RSpec::Puppet::Yaml::DataHelpers.get_named_value(
    'name',
    transform_hash
  )

  if hash_name.nil? || hash_name.empty?
    # Could be an implicit name when { 'implicit' => { 'key' => 'val' } }
    if 1 == transform_hash.keys.count
      first_value = transform_hash.values[0]
      if !first_value.nil? && !first_value.is_a?(Hash)
        raise ArgumentError, "Cannot transform implicitly named Hash when its value is neither nil nor a Hash."
      else
        hash_name        = transform_hash.keys.first
        hash_value       = first_value ||= {}
        transformed_hash = hash_value
          .select {|k,v| k != :name}
          .merge({'name' => hash_name})
      end
    else
      # No explicit or implicit name
      raise ArgumentError, "Hash has neither an explicit nor implicit name."
    end
  else
    transformed_hash = transform_hash
      .select {|k,v| k != :name}
      .merge({'name' => hash_name})
  end

  transformed_hash
end