class Hash

Mu extensions to Ruby's {Hash} type for internal Mu use

Public Class Methods

bok_minimize(o) click to toggle source

Strip extraneous fields out of a {MU::Config} hash to make it suitable for shorthand printing, such as with mu-adopt --diff

# File modules/mu.rb, line 42
  def self.bok_minimize(o)
    if o.is_a?(Hash)
      newhash = o.reject { |k, v|
        !v.is_a?(Array) and !v.is_a?(Hash) and !["name", "id", "cloud_id"].include?(k)
      }
#      newhash.delete("cloud_id") if newhash["name"] or newhash["id"]
      newhash.each_pair { |k, v|
        newhash[k] = bok_minimize(v)
      }
      newhash.reject! { |_k, v| v.nil? or v.empty? }
      newhash = newhash.values.first if newhash.size == 1
      return newhash
    elsif o.is_a?(Array)
      newarray = []
      o.each { |v|
        newvalue = bok_minimize(v)
        newarray << newvalue if !newvalue.nil? and !newvalue.empty?
      }
      newarray = newarray.first if newarray.size == 1
      return newarray
    end

    o
  end

Public Instance Methods

<=>(other) click to toggle source

A comparison function for sorting arrays of hashes

# File modules/mu.rb, line 68
def <=>(other)
  return 1 if other.nil? or self.size > other.size
  return -1 if other.size > self.size
  # Sort any array children we have
  self.each_pair { |k, v|
    self[k] = v.sort if v.is_a?(Array)
  }
  other.each_pair { |k, v|
    other[k] = v.sort if v.is_a?(Array)
  }
  return 0 if self == other # that was easy!
  # compare elements and decide who's "bigger" based on their totals?

  # fine, try some brute force and just hope everything implements to_s
  self.flatten.map { |e| e.to_s }.join() <=> other.flatten.map { |e| e.to_s }.join()
end
deep_merge!(with, on = self) click to toggle source

Implement a merge! that just updates each hash leaf as needed, not trashing the branch on the way there.

# File modules/mu.rb, line 185
def deep_merge!(with, on = self)

  if on and with and with.is_a?(Hash)
    with.each_pair { |k, v|
      if !on[k] or !on[k].is_a?(Hash)
        on[k] = v
      else
        deep_merge!(with[k], on[k])
      end
    }
  elsif with
    on = with
  end

  on
end
diff(with, on = self, level: 0, parents: [], report: {}, habitat: nil) click to toggle source

Recursively compare two Mu Basket of Kittens hashes and report the differences

# File modules/mu.rb, line 86
def diff(with, on = self, level: 0, parents: [], report: {}, habitat: nil)
  return if with.nil? and on.nil?
  if with.nil? or on.nil? or with.class != on.class
    return # XXX ...however we're flagging differences
  end
  return if on == with

  changes = []
  report ||= {}
  if on.is_a?(Hash)
    on_unique = (on.keys - with.keys)
    with_unique = (with.keys - on.keys)
    shared = (with.keys & on.keys)
    shared.each { |k|

      report_data = diff(with[k], on[k], level: level+1, parents: parents + [k], report: report[k], habitat: habitat)
      if report_data and !report_data.empty?
        report ||= {}
        report[k] = report_data
      end
    }
    on_unique.each { |k|
      report[k] = { :action => :removed, :parents => parents, :value => on[k].clone }
      report[k][:habitat] = habitat if habitat
    }
    with_unique.each { |k|
      report[k] = { :action => :added, :parents => parents, :value => with[k].clone }
      report[k][:habitat] = habitat if habitat
    }
  elsif on.is_a?(Array)
    return if with == on
    # special case- Basket of Kittens lists of declared resources of a type;
    # we use this to decide if we can compare two array elements as if they
    # should be equivalent
    # We also implement comparison operators for {Hash} and our various
    # custom objects which we might find in here so that we can get away with
    # sorting arrays full of weird, non-primitive types.
    done = []
    on.sort.each { |elt|
      if elt.is_a?(Hash) and !MU::MommaCat.getChunkName(elt).first.nil?
        elt_namestr, elt_location, elt_location_list = MU::MommaCat.getChunkName(elt)

        with.sort.each { |other_elt|
          other_elt_namestr, other_elt_location, other_elt_location_list = MU::MommaCat.getChunkName(other_elt)

          # Case 1: The array element exists in both version of this array
          if elt_namestr and other_elt_namestr and
             elt_namestr == other_elt_namestr and
             (elt_location.nil? or other_elt_location.nil? or
              elt_location == other_elt_location or
              !(elt_location_list & other_elt_location_list).empty?
             )
            done << elt
            done << other_elt
            break if elt == other_elt # if they're identical, we're done
            report_data = diff(other_elt, elt, level: level+1, parents: parents + [elt_namestr], habitat: (elt_location || habitat))
            if report_data and !report_data.empty?
              report ||= {}
              report[elt_namestr] = report_data
            end
            break
          end
        }
      end
    }
    on_unique = (on - with) - done
    with_unique = (with - on) - done

    # Case 2: This array entry exists in the old version, but not the new one
    on_unique.each { |e|
      namestr, loc = MU::MommaCat.getChunkName(e)

      report ||= {}
      report[namestr] = { :action => :removed, :parents => parents, :value => e.clone }
      report[namestr][:habitat] = loc if loc
    }

    # Case 3: This array entry exists in the new version, but not the old one
    with_unique.each { |e|
      namestr, loc = MU::MommaCat.getChunkName(e)

      report ||= {}
      report[namestr] = { :action => :added, :parents => parents, :value => e.clone }
      report[namestr][:habitat] = loc if loc
    }

  # A plain old leaf node of data
  else
    if on != with
      report = { :action => :changed, :parents => parents, :oldvalue => on, :value => with.clone }
      report[:habitat] = habitat if habitat
    end
  end

  report.freeze
end