Attention une mise à jour du service Gitlab va être effectuée le mardi 14 décembre entre 13h30 et 14h00. Cette mise à jour va générer une interruption du service dont nous ne maîtrisons pas complètement la durée mais qui ne devrait pas excéder quelques minutes.

hash.rb 5.75 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
# prepend header
def add_header(output_file)
    header_template = <<-eol  
#
44
# This file was generated by reference-repository.git
45 46 47 48 49 50 51 52 53
# 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