Skip to content
This repository
tag: v3.2.3
Fetching contributors…

Cannot retrieve contributors at this time

file 222 lines (181 sloc) 5.151 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 52 53 54 55 56 57 58 59 60 61 62 63 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 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 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
begin
  require 'psych'
rescue LoadError
end

require 'yaml'

YAML.add_builtin_type("omap") do |type, val|
  ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }]
end

module ActiveSupport
  # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
  # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
  # implements a hash that preserves insertion order, as in Ruby 1.9:
  #
  # oh = ActiveSupport::OrderedHash.new
  # oh[:a] = 1
  # oh[:b] = 2
  # oh.keys # => [:a, :b], this order is guaranteed
  #
  # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
  class OrderedHash < ::Hash
    def to_yaml_type
      "!tag:yaml.org,2002:omap"
    end

    def encode_with(coder)
      coder.represent_seq '!omap', map { |k,v| { k => v } }
    end

    def to_yaml(opts = {})
      if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
        return super
      end

      YAML.quick_emit(self, opts) do |out|
        out.seq(taguri) do |seq|
          each do |k, v|
            seq.add(k => v)
          end
        end
      end
    end

    def nested_under_indifferent_access
      self
    end

    # Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt>
    def extractable_options?
      true
    end

    # Hash is ordered in Ruby 1.9!
    if RUBY_VERSION < '1.9'

      # In MRI the Hash class is core and written in C. In particular, methods are
      # programmed with explicit C function calls and polymorphism is not honored.
      #
      # For example, []= is crucial in this implementation to maintain the @keys
      # array but hash.c invokes rb_hash_aset() originally. This prevents method
      # reuse through inheritance and forces us to reimplement stuff.
      #
      # For instance, we cannot use the inherited #merge! because albeit the algorithm
      # itself would work, our []= is not being called at all by the C code.

      def initialize(*args, &block)
        super
        @keys = []
      end

      def self.[](*args)
        ordered_hash = new

        if (args.length == 1 && args.first.is_a?(Array))
          args.first.each do |key_value_pair|
            next unless (key_value_pair.is_a?(Array))
            ordered_hash[key_value_pair[0]] = key_value_pair[1]
          end

          return ordered_hash
        end

        unless (args.size % 2 == 0)
          raise ArgumentError.new("odd number of arguments for Hash")
        end

        args.each_with_index do |val, ind|
          next if (ind % 2 != 0)
          ordered_hash[val] = args[ind + 1]
        end

        ordered_hash
      end

      def initialize_copy(other)
        super
        # make a deep copy of keys
        @keys = other.keys
      end

      def []=(key, value)
        @keys << key unless has_key?(key)
        super
      end

      def delete(key)
        if has_key? key
          index = @keys.index(key)
          @keys.delete_at index
        end
        super
      end

      def delete_if
        super
        sync_keys!
        self
      end

      def reject!
        super
        sync_keys!
        self
      end

      def reject(&block)
        dup.reject!(&block)
      end

      def keys
        @keys.dup
      end

      def values
        @keys.collect { |key| self[key] }
      end

      def to_hash
        self
      end

      def to_a
        @keys.map { |key| [ key, self[key] ] }
      end

      def each_key
        return to_enum(:each_key) unless block_given?
        @keys.each { |key| yield key }
        self
      end

      def each_value
        return to_enum(:each_value) unless block_given?
        @keys.each { |key| yield self[key]}
        self
      end

      def each
        return to_enum(:each) unless block_given?
        @keys.each {|key| yield [key, self[key]]}
        self
      end

      def each_pair
        return to_enum(:each_pair) unless block_given?
        @keys.each {|key| yield key, self[key]}
        self
      end

      alias_method :select, :find_all

      def clear
        super
        @keys.clear
        self
      end

      def shift
        k = @keys.first
        v = delete(k)
        [k, v]
      end

      def merge!(other_hash)
        if block_given?
          other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
        else
          other_hash.each { |k, v| self[k] = v }
        end
        self
      end

      alias_method :update, :merge!

      def merge(other_hash, &block)
        dup.merge!(other_hash, &block)
      end

      # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
      def replace(other)
        super
        @keys = other.keys
        self
      end

      def invert
        OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
      end

      def inspect
        "#<OrderedHash #{super}>"
      end

      private
        def sync_keys!
          @keys.delete_if {|k| !has_key?(k)}
        end
    end
  end
end
Something went wrong with that request. Please try again.