File: stringify-hash.rb

package info (click to toggle)
ruby-stringify-hash 0.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 116 kB
  • sloc: ruby: 181; makefile: 3
file content (299 lines) | stat: -rw-r--r-- 8,361 bytes parent folder | download | duplicates (3)
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
require 'stringify-hash/version'
# A hash that treats Symbol and String keys interchangeably
# and recursively merges hashes
class StringifyHash < Hash

  # The dividor between elements when StringifyHash is dumped
  DIV = '    '

  # The end of line when dumping
  EOL = "\n"

  # Get value for given key, search for both k as String and k as Symbol,
  # if not present return nil
  #
  # @param [Object] k The key to find, searches for both k as String
  #                   and k as Symbol
  #
  # @example Use this method to return the value for a given key
  #     a['key'] = 'value'
  #     a['key'] == a[:key] == 'value'
  #
  # @return [nil, Object] Return the Object found at given key,
  #                       or nil if no Object found
  def [] k
    super(k.to_s) || super(k.to_sym)
  end

  # Set Symbol key to Object value
  # @param [Object] k The key to associated with the value,
  #                   converted to Symbol key
  # @param [Object] v The value to store in the ObjectHash
  #
  # @example Use this method to set the value for a key
  #     a['key'] = 'value'
  #
  # @return [Object] Return the Object value just stored
  def []=k,v
    super(k.to_sym, v)
  end

  # Determine if key is stored in ObjectHash
  # @param [Object] k The key to find in ObjectHash, searches for
  #                   both k as String and k as Symbol
  #
  # @example Use this method to set the value for a key
  #     a['key'] = 'value'
  #     a.has_key[:key] == true
  #
  # @return [Boolean]
  def has_key? k
    super(k.to_s) || super(k.to_sym)
  end

  # Determine key=>value entry in StringifyHash, remove both value at
  # String key and value at Symbol key
  #
  # @param [Object] k The key to delete in ObjectHash,
  # deletes both k as String and k as Symbol
  #
  # @example Use this method to set the value for a key
  #     a['key'] = 'value'
  #     a.delete[:key] == 'value'
  #
  # @return [Object, nil] The Object deleted at value,
  # nil if no Object deleted
  def delete k
    super(k.to_s) || super(k.to_sym)
  end

  # Recursively merge and StringifyHash with an OptionsHash or Hash
  #
  # @param [StringifyHash]       base The hash to merge into
  # @param [StringifyHash, Hash] hash The hash to merge from
  #
  # @example
  #   base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
  #   hash = { :key => { :subkey1 => 'newval'} }
  #
  #   rmerge(base, hash)
  #   #=>  {:key =>
  #           {:subkey1 => 'newval',
  #            :subkey2 => 'subval'}}
  #
  # @return [StringifyHash] The combined bash and hash
  def rmerge base, hash
    return base unless hash.is_a?(Hash) || hash.is_a?(self.class)
    hash.each do |key, v|
      if (base[key].is_a?(Hash) || base[key].is_a?(self.class)) && (hash[key].is_a?(Hash) || hash[key].is_a?(self.class))
        rmerge(base[key], hash[key])
      elsif hash[key].is_a?(Hash)
        base[key] = self.class.new.merge(hash[key])
      else
        base[key]= hash[key]
      end
    end
    base
  end

  # Create new StringifyHash from recursively merged self with an OptionsHash or Hash
  #
  # @param [StringifyHash, Hash] hash The hash to merge from
  #
  # @example
  #   base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
  #   hash = { :key => { :subkey1 => 'newval'} }
  #
  #   base.merge(hash)
  #   #=> {:key =>
  #         {:subkey1 => 'newval',
  #          :subkey2 => 'subval' }
  #
  # @return [StringifyHash] The combined hash
  def merge hash
    #make a deep copy into an empty hash object
    merged_hash = rmerge(self.class.new, self)
    rmerge(merged_hash, hash)
  end

  # Recursively merge self with an StringifyHash or Hash
  #
  # @param [StringifyHash, Hash] hash The hash to merge from
  #
  # @example
  #   base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
  #   hash = { :key => { :subkey1 => 'newval'} }
  #
  #   base.merge!(hash)
  #   #=> {:key =>
  #         {:subkey1 => 'newval',
  #          :subkey2 => 'subval' }
  #
  # @return [StringifyHash] The combined hash
  def merge! hash
    rmerge(self, hash)
  end

  # Helper for formatting collections
  # Computes the indentation level for elements of the collection
  # Yields indentation to block to so the caller can create
  # map of element strings
  # Places delimiters in the correct location
  # Joins everything with correct EOL
  #
  #
  # !@visibility private
  def as_coll( opening, closing, in_lvl, in_inc, &block )
    delim_indent = in_inc * in_lvl
    elem_indent  = in_inc * (in_lvl + 1)

    open_brace  = opening
    close_brace = delim_indent + closing

    fmtd_coll = block.call( elem_indent )
    str_coll = fmtd_coll.join( ',' + EOL )

    return open_brace + EOL + str_coll + EOL + close_brace
  end

  # Pretty prints a collection
  #
  # @param [Enumerable] collection The collection to be printed
  # @param [Integer]    in_lvl     The level of indentation
  # @param [String]     in_inc     The increment to indent
  #
  # @example
  #   base = {:key => { :subkey1 => 'subval', :subkey2 => ['subval'] }}
  #   self.fmt_collection( base )
  #   #=> '{
  #            "key": {
  #                "subkey": "subval",
  #                "subkey2": [
  #                    "subval"
  #                ]
  #            }
  #        }'
  #
  # @return [String] The collection as a pretty JSON object
  def fmt_collection( collection, in_lvl = 0, in_inc = DIV )
    if collection.respond_to? :each_pair
      string = fmt_assoc( collection, in_lvl, in_inc )
    else
      string = fmt_list( collection, in_lvl, in_inc )
    end

    return string
  end

  # Pretty prints an associative collection
  #
  # @param [#each_pair] coll    The collection to be printed
  # @param [Integer]    in_lvl  The level of indentation
  # @param [String]     in_inc  The increment to indent
  #
  # @example
  #   base = { :key => 'value', :key2 => 'value' }
  #   self.fmt_assoc( base )
  #   #=> '{
  #            "key": "value",
  #            "key2": "value"
  #        }'
  #
  # @return [String] The collection as a pretty JSON object
  def fmt_assoc( coll, in_lvl = 0, in_inc = DIV )
    if coll.empty?
      return '{}'
    else
      as_coll '{', '}', in_lvl, in_inc do |elem_indent|
        coll.map do |key, value|
          assoc_line = elem_indent + '"' + key.to_s + '"' + ': '
          assoc_line += fmt_value( value, in_lvl, in_inc )
        end
      end
    end
  end

  # Pretty prints a list collection
  #
  # @param [#each]    coll    The collection to be printed
  # @param [Integer]  in_lvl  The level of indentation
  # @param [String]   in_inc  The increment to indent
  #
  # @example
  #   base = [ 'first', 'second' ]
  #   self.fmt_list( base )
  #   #=> '[
  #            "first",
  #            "second"
  #        ]'
  #
  # @return [String] The collection as a pretty JSON object
  def fmt_list( coll, in_lvl = 0, in_inc = DIV )
    if coll.empty?
      return '[]'
    else
      as_coll '[', ']', in_lvl, in_inc do |indent|
        coll.map do |el|
          indent + fmt_value( el, in_lvl, in_inc )
        end
      end
    end
  end

  # Chooses between collection and primitive formatting
  #
  # !@visibility private
  def fmt_value( value, in_lvl = 0, in_inc = DIV )
    if value.kind_of? Enumerable and not value.is_a? String
      fmt_collection( value, in_lvl + 1, in_inc )
    else
      fmt_basic( value )
    end
  end

  # Pretty prints primitive JSON values
  #
  # @param [Object] value The collection to be printed
  #
  # @example
  #   self.fmt_value( 4 )
  #   #=> '4'
  #
  # @example
  #   self.fmt_value( true )
  #   #=> 'true'
  #
  # @example
  #   self.fmt_value( nil )
  #   #=> 'null'
  #
  # @example
  #   self.fmt_value( 'string' )
  #   #=> '"string"'
  #
  # @return [String] The value as a valid JSON primitive
  def fmt_basic( value )
    case value
    when Numeric, TrueClass, FalseClass then value.to_s
    when NilClass then "null"
    else "\"#{value}\""
    end
  end

  # Pretty print the options as JSON
  #
  # @example
  #   base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
  #   base.dump
  #   #=>  '{
  #             "key": {
  #                 "subkey1": "subval",
  #                 "subkey2": 2
  #             }
  #         }
  #
  # @return [String] The description of self
  def dump
    fmt_collection( self, 0, DIV )
  end
end