hashie 2.1.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +524 -59
  3. data/CONTRIBUTING.md +24 -7
  4. data/README.md +781 -90
  5. data/Rakefile +19 -2
  6. data/UPGRADING.md +245 -0
  7. data/hashie.gemspec +21 -13
  8. data/lib/hashie.rb +60 -21
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +96 -33
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +124 -18
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
  17. data/lib/hashie/extensions/dash/property_translation.rb +191 -0
  18. data/lib/hashie/extensions/deep_fetch.rb +7 -5
  19. data/lib/hashie/extensions/deep_find.rb +69 -0
  20. data/lib/hashie/extensions/deep_locate.rb +113 -0
  21. data/lib/hashie/extensions/deep_merge.rb +35 -12
  22. data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
  23. data/lib/hashie/extensions/indifferent_access.rb +28 -16
  24. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  25. data/lib/hashie/extensions/key_conversion.rb +0 -82
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +18 -0
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +154 -11
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
  33. data/lib/hashie/extensions/pretty_inspect.rb +19 -0
  34. data/lib/hashie/extensions/ruby_version.rb +60 -0
  35. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  36. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  37. data/lib/hashie/extensions/stringify_keys.rb +71 -0
  38. data/lib/hashie/extensions/symbolize_keys.rb +71 -0
  39. data/lib/hashie/hash.rb +27 -8
  40. data/lib/hashie/logger.rb +18 -0
  41. data/lib/hashie/mash.rb +235 -57
  42. data/lib/hashie/railtie.rb +21 -0
  43. data/lib/hashie/rash.rb +40 -16
  44. data/lib/hashie/trash.rb +2 -88
  45. data/lib/hashie/utils.rb +44 -0
  46. data/lib/hashie/version.rb +1 -1
  47. metadata +42 -81
  48. data/.gitignore +0 -9
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -36
  51. data/.travis.yml +0 -15
  52. data/Gemfile +0 -11
  53. data/Guardfile +0 -5
  54. data/lib/hashie/hash_extensions.rb +0 -47
  55. data/spec/hashie/clash_spec.rb +0 -48
  56. data/spec/hashie/dash_spec.rb +0 -338
  57. data/spec/hashie/extensions/coercion_spec.rb +0 -156
  58. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
  59. data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
  60. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
  61. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
  62. data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
  63. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  64. data/spec/hashie/extensions/method_access_spec.rb +0 -121
  65. data/spec/hashie/hash_spec.rb +0 -66
  66. data/spec/hashie/mash_spec.rb +0 -467
  67. data/spec/hashie/rash_spec.rb +0 -44
  68. data/spec/hashie/trash_spec.rb +0 -193
  69. data/spec/hashie/version_spec.rb +0 -7
  70. data/spec/spec.opts +0 -3
  71. data/spec/spec_helper.rb +0 -8
@@ -0,0 +1,61 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Allow a Mash to properly respond to everything
5
+ #
6
+ # By default, Mashes only say they respond to methods for keys that exist
7
+ # in their key set or any of the affix methods (e.g. setter, underbang,
8
+ # etc.). This causes issues when you try to use them within a
9
+ # SimpleDelegator or bind to a method for a key that is unset.
10
+ #
11
+ # This extension allows a Mash to properly respond to `respond_to?` and
12
+ # `method` for keys that have not yet been set. This enables full
13
+ # compatibility with SimpleDelegator and thunk-oriented programming.
14
+ #
15
+ # There is a trade-off with this extension: it will run slower than a
16
+ # regular Mash; insertions and initializations with keys run approximately
17
+ # 20% slower and cost approximately 19KB of memory per class that you
18
+ # make permissive.
19
+ #
20
+ # @api public
21
+ # @example Make a new, permissively responding Mash subclass
22
+ # class PermissiveMash < Hashie::Mash
23
+ # include Hashie::Extensions::Mash::PermissiveRespondTo
24
+ # end
25
+ #
26
+ # mash = PermissiveMash.new(a: 1)
27
+ # mash.respond_to? :b #=> true
28
+ module PermissiveRespondTo
29
+ # The Ruby hook for behavior when including the module
30
+ #
31
+ # @api private
32
+ # @private
33
+ # @return void
34
+ def self.included(base)
35
+ base.instance_variable_set :@_method_cache, base.instance_methods
36
+ base.define_singleton_method(:method_cache) { @_method_cache }
37
+ end
38
+
39
+ # The Ruby hook for determining what messages a class might respond to
40
+ #
41
+ # @api private
42
+ # @private
43
+ def respond_to_missing?(_method_name, _include_private = false)
44
+ true
45
+ end
46
+
47
+ private
48
+
49
+ # Override the Mash logging behavior to account for permissiveness
50
+ #
51
+ # @api private
52
+ # @private
53
+ def log_collision?(method_key)
54
+ self.class.method_cache.include?(method_key) &&
55
+ !self.class.disable_warnings?(method_key) &&
56
+ !(regular_key?(method_key) || regular_key?(method_key.to_s))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,18 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ module SafeAssignment
5
+ def custom_writer(key, *args) #:nodoc:
6
+ if !key?(key) && respond_to?(key, true)
7
+ raise ArgumentError, "The property #{key} clashes with an existing method."
8
+ end
9
+ super
10
+ end
11
+
12
+ def []=(*args)
13
+ custom_writer(*args)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Overrides Mash's default behavior of converting keys to strings
5
+ #
6
+ # @example
7
+ # class LazyResponse < Hashie::Mash
8
+ # include Hashie::Extensions::Mash::SymbolizedKeys
9
+ # end
10
+ #
11
+ # response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
12
+ # #=> {id: 123, name: "Rey"}
13
+ #
14
+ # @api public
15
+ module SymbolizeKeys
16
+ # Hook for being included in a class
17
+ #
18
+ # @api private
19
+ # @return [void]
20
+ # @raise [ArgumentError] when the base class isn't a Mash
21
+ def self.included(base)
22
+ raise ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
23
+ end
24
+
25
+ private
26
+
27
+ # Converts a key to a symbol
28
+ #
29
+ # @api private
30
+ # @param [String, Symbol] key the key to convert to a symbol
31
+ # @return [void]
32
+ def convert_key(key)
33
+ key.to_sym
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -27,15 +27,25 @@ module Hashie
27
27
  #
28
28
  # user.not_declared # => NoMethodError
29
29
  module MethodReader
30
- def respond_to?(name, include_private = false)
30
+ def respond_to_missing?(name, include_private = false)
31
31
  return true if key?(name.to_s) || key?(name.to_sym)
32
32
  super
33
33
  end
34
34
 
35
35
  def method_missing(name, *args)
36
- return self[name.to_s] if key?(name.to_s)
37
- return self[name.to_sym] if key?(name.to_sym)
38
- super
36
+ if key?(name)
37
+ self[name]
38
+ else
39
+ sname = name.to_s
40
+ if key?(sname)
41
+ self[sname]
42
+ elsif sname[-1] == '?'
43
+ kname = sname[0..-2]
44
+ key?(kname) || key?(kname.to_sym)
45
+ else
46
+ super
47
+ end
48
+ end
39
49
  end
40
50
  end
41
51
 
@@ -57,7 +67,7 @@ module Hashie
57
67
  # h['awesome'] # => 'sauce'
58
68
  #
59
69
  module MethodWriter
60
- def respond_to?(name, include_private = false)
70
+ def respond_to_missing?(name, include_private = false)
61
71
  return true if name.to_s =~ /=$/
62
72
  super
63
73
  end
@@ -96,17 +106,42 @@ module Hashie
96
106
  # h.def? # => false
97
107
  # h.hji? # => NoMethodError
98
108
  module MethodQuery
99
- def respond_to?(name, include_private = false)
100
- return true if name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym))
101
- super
109
+ def respond_to_missing?(name, include_private = false)
110
+ if query_method?(name) && indifferent_key?(key_from_query_method(name))
111
+ true
112
+ else
113
+ super
114
+ end
102
115
  end
103
116
 
104
117
  def method_missing(name, *args)
105
- if args.empty? && name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym))
106
- return self[Regexp.last_match[1]] || self[Regexp.last_match[1].to_sym]
118
+ return super unless args.empty?
119
+
120
+ if query_method?(name)
121
+ key = key_from_query_method(name)
122
+ if indifferent_key?(key)
123
+ !!(self[key] || self[key.to_sym])
124
+ else
125
+ super
126
+ end
127
+ else
128
+ super
107
129
  end
130
+ end
108
131
 
109
- super
132
+ private
133
+
134
+ def indifferent_key?(name)
135
+ name = name.to_s
136
+ key?(name) || key?(name.to_sym)
137
+ end
138
+
139
+ def key_from_query_method(query_method)
140
+ query_method.to_s[0..-2]
141
+ end
142
+
143
+ def query_method?(name)
144
+ name.to_s.end_with?('?')
110
145
  end
111
146
  end
112
147
 
@@ -120,5 +155,113 @@ module Hashie
120
155
  end
121
156
  end
122
157
  end
158
+
159
+ # A module shared between MethodOverridingWriter and MethodOverridingInitializer
160
+ # to contained shared logic. This module aids in redefining existing hash methods.
161
+ module RedefineMethod
162
+ protected
163
+
164
+ def method?(name)
165
+ methods.map(&:to_s).include?(name)
166
+ end
167
+
168
+ def redefine_method(method_name)
169
+ eigenclass = class << self; self; end
170
+ eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
171
+ eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
172
+ end
173
+ end
174
+
175
+ # MethodOverridingWriter gives you #key_name= shortcuts for
176
+ # writing to your hash. It allows methods to be overridden by
177
+ # #key_name= shortcuts and aliases those methods with two
178
+ # leading underscores.
179
+ #
180
+ # Keys are written as strings. Override #convert_key if you
181
+ # would like to have symbols or something else.
182
+ #
183
+ # Note that MethodOverridingWriter also overrides
184
+ # #respond_to_missing? such that any #method_name= will respond
185
+ # appropriately as true.
186
+ #
187
+ # @example
188
+ # class MyHash < Hash
189
+ # include Hashie::Extensions::MethodOverridingWriter
190
+ # end
191
+ #
192
+ # h = MyHash.new
193
+ # h.awesome = 'sauce'
194
+ # h['awesome'] # => 'sauce'
195
+ # h.zip = 'a-dee-doo-dah'
196
+ # h.zip # => 'a-dee-doo-dah'
197
+ # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
198
+ #
199
+ module MethodOverridingWriter
200
+ include RedefineMethod
201
+
202
+ def convert_key(key)
203
+ key.to_s
204
+ end
205
+
206
+ def method_missing(name, *args)
207
+ if args.size == 1 && name.to_s =~ /(.*)=$/
208
+ key = Regexp.last_match[1]
209
+ redefine_method(key) if method?(key) && !already_overridden?(key)
210
+ return self[convert_key(key)] = args.first
211
+ end
212
+
213
+ super
214
+ end
215
+
216
+ def respond_to_missing?(name, include_private = false)
217
+ return true if name.to_s.end_with?('=')
218
+ super
219
+ end
220
+
221
+ protected
222
+
223
+ def already_overridden?(name)
224
+ method?("__#{name}")
225
+ end
226
+ end
227
+
228
+ # A macro module that will automatically include MethodReader,
229
+ # MethodOverridingWriter, and MethodQuery, giving you the ability
230
+ # to read, write, and query keys in a hash using method call
231
+ # shortcuts that can override object methods. Any overridden
232
+ # object method is automatically aliased with two leading
233
+ # underscores.
234
+ module MethodAccessWithOverride
235
+ def self.included(base)
236
+ [MethodReader, MethodOverridingWriter,
237
+ MethodQuery, MethodOverridingInitializer].each do |mod|
238
+ base.send :include, mod
239
+ end
240
+ end
241
+ end
242
+
243
+ # MethodOverridingInitializer allows you to override default hash
244
+ # methods when passing in values from an existing hash. The overriden
245
+ # methods are aliased with two leading underscores.
246
+ #
247
+ # @example
248
+ # class MyHash < Hash
249
+ # include Hashie::Extensions::MethodOverridingInitializer
250
+ # end
251
+ #
252
+ # h = MyHash.new(zip: 'a-dee-doo-dah')
253
+ # h.zip # => 'a-dee-doo-dah'
254
+ # h.__zip # => [[['zip', 'a-dee-doo-dah']]]
255
+ module MethodOverridingInitializer
256
+ include RedefineMethod
257
+
258
+ def initialize(hash = {})
259
+ hash.each do |key, value|
260
+ skey = key.to_s
261
+ redefine_method(skey) if method?(skey)
262
+ self[skey] = value
263
+ end
264
+ end
265
+ end
123
266
  end
124
267
  end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'pathname'
4
+
5
+ module Hashie
6
+ module Extensions
7
+ module Parsers
8
+ class YamlErbParser
9
+ def initialize(file_path, options = {})
10
+ @content = File.read(file_path)
11
+ @file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
12
+ @options = options
13
+ end
14
+
15
+ def perform
16
+ template = ERB.new(@content)
17
+ template.filename = @file_path
18
+ permitted_classes = @options.fetch(:permitted_classes) { [] }
19
+ permitted_symbols = @options.fetch(:permitted_symbols) { [] }
20
+ aliases = @options.fetch(:aliases) { true }
21
+
22
+ yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
23
+ end
24
+
25
+ def self.perform(file_path, options = {})
26
+ new(file_path, options).perform
27
+ end
28
+
29
+ private
30
+
31
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0') # Ruby 2.6+
32
+ def yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
33
+ YAML.safe_load(
34
+ template.result,
35
+ permitted_classes: permitted_classes,
36
+ permitted_symbols: permitted_symbols,
37
+ aliases: aliases
38
+ )
39
+ end
40
+ else
41
+ def yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
42
+ YAML.safe_load(template.result, permitted_classes, permitted_symbols, aliases)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module Hashie
2
+ module Extensions
3
+ module PrettyInspect
4
+ def self.included(base)
5
+ base.send :alias_method, :hash_inspect, :inspect
6
+ base.send :alias_method, :inspect, :hashie_inspect
7
+ end
8
+
9
+ def hashie_inspect
10
+ ret = "#<#{self.class}"
11
+ keys.sort_by(&:to_s).each do |key|
12
+ ret << " #{key}=#{self[key].inspect}"
13
+ end
14
+ ret << '>'
15
+ ret
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (c) Chad Fowler, Rich Kilmer, Jim Weirich and others.
2
+ # Portions copyright (c) Engine Yard and Andre Arko
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+
15
+ module Hashie
16
+ module Extensions
17
+ class RubyVersion
18
+ include Comparable
19
+
20
+ attr_accessor :segments
21
+
22
+ def initialize(version)
23
+ @segments = split_to_segments(version)
24
+ end
25
+
26
+ def <=>(other)
27
+ lhsegments = segments
28
+ rhsegments = other.segments
29
+
30
+ lhsize = lhsegments.size
31
+ rhsize = rhsegments.size
32
+ limit = (lhsize > rhsize ? lhsize : rhsize) - 1
33
+
34
+ i = 0
35
+
36
+ while i <= limit
37
+ lhs = lhsegments[i] || 0
38
+ rhs = rhsegments[i] || 0
39
+ i += 1
40
+
41
+ next if lhs == rhs
42
+ return -1 if lhs.is_a?(String) && rhs.is_a?(Numeric)
43
+ return 1 if lhs.is_a?(Numeric) && rhs.is_a?(String)
44
+
45
+ return lhs <=> rhs
46
+ end
47
+
48
+ 0
49
+ end
50
+
51
+ private
52
+
53
+ def split_to_segments(version)
54
+ version.scan(/[0-9]+|[a-z]+/i).map do |segment|
55
+ /^\d+$/ =~ segment ? segment.to_i : segment
56
+ end.freeze
57
+ end
58
+ end
59
+ end
60
+ end