Attention une mise à jour du serveur va être effectuée le lundi 17 mai entre 13h et 13h30. Cette mise à jour va générer une interruption du service de quelques minutes.

hash.rb 5.81 KB
Newer Older
Florent Didier's avatar
Florent Didier committed
1
# coding: utf-8
2
# Monkey patching Ruby's Hash class
3

4 5 6 7 8 9 10 11 12 13 14
# Merge a and b. If a and b are hashes, they are recursively merged.
# - a and b might be strings or nil. 
# - b values have the highest priority (if not nil).
def deep_merge_entries(a, b)
  if b.is_a?(Hash)
    a.is_a?(Hash) ? a.deep_merge(b) : b
  else
    b.nil? ? a : b
  end
end

15 16 17 18 19 20 21 22 23 24 25
def rec_sort(h)
  case h
  when Array
    h.map{|v| rec_sort(v)}#.sort_by!{|v| (v.to_s rescue nil) }
  when Hash
    Hash[Hash[h.map{|k,v| [rec_sort(k),rec_sort(v)]}].sort_by{|k,v| [(k.to_s rescue nil), (v.to_s rescue nil)]}]
  else
    h
  end
end

26
# Write pretty and sorted JSON files
27 28
def write_json(filepath, data, perm = 0644)
  File.open(filepath, 'w', perm) do |f|
29 30 31 32 33
    f.write(JSON.pretty_generate(rec_sort(data)))
  end
end

# Write sorted YAML files
34 35
def write_yaml(filepath, data, perm = 0644)
  File.open(filepath, 'w', perm) do |f|
36 37 38 39
    f.write(rec_sort(data).to_yaml)
  end
end

40 41 42 43 44 45 46 47 48 49 50 51 52 53
# prepend header
def add_header(output_file)
    header_template = <<-eol  
#
# This file was generated by reference-repository.git/generators/puppet/<%= File.basename($PROGRAM_NAME) %>
# Do not edit this file by hand. Your changes will be overwritten.
#
eol

  header = ERB.new(header_template).result()
  contents = File.read(output_file)
  File.write(output_file, header + "\n" + contents)
end

54 55 56
# Extend Hash with helper methods needed to convert input data files to ruby Hash
class ::Hash

57 58 59 60
  # Recursively merge this Hash with another (ie. merge nested hash)
  # Returns a new hash containing the contents of other_hash and the contents of hash. The value for entries with duplicate keys will be that of other_hash:
  # a = {"key": "value_a"}
  # b = {"key": "value_b"}
61 62
  # a.deep_merge(b) -> {:key=>"value_b"}
  # b.deep_merge(a) -> {:key=>"value_a"}
63
  def deep_merge(other_hash)
64
    merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
65
    self.merge(other_hash, &merger)
66 67
  end

68
  # Merge keys that match "PREFIX[a-b]" with others keys that begins by "PREFIX" 
69 70
  # and that ends with x, where a<=x<=b.
  # - This is done recursively (for this Hash and every Hashes it may contain).
71 72
  # - PREFIX[a-b] values have lower priority on existing PREFIXx keys.
  # - "a" and/or "b" may be omited (ie. "PREFIX[a-]", "PREFIX[-b]" or "PREFIX[-]"), meaning that there are no lower and/or upper bound for x.
73
  #   * If only a is omited, a == 1.
74
  #   * If b is omited, only existing keys are modified (no keys are created). Otherwise, PREFIX[a] to PREFIX[b] entries are created (if missing).
75
  # Example:
76
  # {"foo-1": {a: 0}, "foo-2": {a: 0}, "foo-3": {a: 0}, "foo-[2-]": {b: 1}}.expand_square_brackets()
77
  #  -> {"foo-1": {a: 0}, "foo-2": {a: 0, b:1},  "foo-3": {a: 0, b: 0}}
78
  def expand_square_brackets(keys=self.clone)
79

80
    # Looking up for PREFIX[a-b] keys
81 82
    # (using .clone because cannot add/remove a key from hash during iteration)
    keys.clone.each { |key_ab, value_ab|
83

84
      prefix, a, b = key_ab.to_s.scan(/^(.*)\[(\d*)-(\d*)\]$/).first
85 86 87 88 89 90 91 92
      next if not a and not b # not found
      a != "" ? a = a.to_i : a = 1
      b != "" ? b = b.to_i : b

      if b != ""

        # Merge keys, creating missing entries if needed.
        (a..b).each { |x|
93
          key = "#{prefix}#{x}"
94 95
          key = key.to_sym if key_ab.is_a?(Symbol)

96
          # For duplicate entries, the value of PREFIXx is kept.
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
97
          self[key] = deep_merge_entries(deep_copy(value_ab), self[key]).clone
98 99 100
        }

      else
101

102
        # Modify only existing keys. Looking up for PREFIXx keys.
103
        self.clone.each { |key_x, value_x|
104
          next if key_x.class != key_ab.class
105
          x = key_x.to_s.scan(/^#{prefix}(\d*)$/).first
106 107 108
          x = x.first if x
          next if not x or x.to_i < a

109
          # For duplicate entries, the value of PREFIXx is kept.
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
110
          self[key_x] = deep_merge_entries(deep_copy(value_ab), value_x).clone
111 112
        }
      end
113

114
      # Delete entry "PREFIX[a-b]"
115
      self.delete(key_ab)
116
      keys.delete(key_ab)
117
    }
118 119

    # Do it recursivly
120
    keys.each { |key, value|
121
      if value.is_a?(Hash)
122
        self[key].expand_square_brackets(value)
123 124 125 126
      end
    }
  end

127
  # Sort a hash according to the position of the key in the array.
128 129 130 131
  def sort_by_array(array)
    Hash[sort_by{|key, _| array.index(key) || length}] 
  end

132 133 134 135 136 137
  # Add an element composed of nested Hashes made from elements found in "array" argument
  # i.e.: from_array([a, b, c],"foo") -> {a: {b: {c: "foo"}}}
  def self.from_array(array, value)
    return array.reverse.inject(value) { |a, n| { n => a } }
  end

Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
138 139 140
  # Custom iterator. Same as "each" but it sorts keys by node_uid (ie. graphene-10 after graphene-9)
  def each_sort_by_node_uid
    self.sort_by { |item| item.to_s.split(/(\d+)/).map { |e| [e.to_i, e] } }.each { |key, value|
141
      yield key, value if key != nil
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
142 143 144 145 146
    }
  end

  # Custom iterator. Only consider entries corresponding to cluster_list and node_list. Sorted by node_uid.
  def each_filtered_node_uid(cluster_list, node_list)
147
    self.each_sort_by_node_uid { |key, properties|
Florent Didier's avatar
Florent Didier committed
148 149
      # As an example, key can be equal to 'grimoire-1' for default resources or
      # ['grimoire-1', 1] for disk resources (disk n°1 of grimoire-1)
150
      node_uid, = key
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
151
      cluster_uid = node_uid.split(/-/).first
152

Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
153 154
      if (! cluster_list || cluster_list.include?(cluster_uid)) &&
          (! node_list || node_list.include?(node_uid))
Florent Didier's avatar
Florent Didier committed
155
        yield key, properties
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
156 157 158 159
      end
    }
  end

160 161
  # Ex: { a: 1, b: nil, c: { d: nil, e: '' } }.deep_reject! { |k, v| v.blank? }
  # ==> { a: 1 }
162

163 164 165 166 167 168 169
  # Note: the blk delete condition is also applied to hash
  #       use  { |k, v| !Hash === v } if you do not want this default behavior
  def deep_reject!(&blk)
    self.each do |k, v|
      v.deep_reject!(&blk) if v.is_a?(Hash)
      self.delete(k) if blk.call(k, v)
    end
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
170 171
  end

172
end
173 174 175 176

def deep_copy(o)
  Marshal.load(Marshal.dump(o))
end
177 178 179 180 181 182

class NilClass
  def clone()
    nil
  end
end