hashie 2.1.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +524 -59
- data/CONTRIBUTING.md +24 -7
- data/README.md +781 -90
- data/Rakefile +19 -2
- data/UPGRADING.md +245 -0
- data/hashie.gemspec +21 -13
- data/lib/hashie.rb +60 -21
- data/lib/hashie/array.rb +21 -0
- data/lib/hashie/clash.rb +24 -12
- data/lib/hashie/dash.rb +96 -33
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/coercion.rb +124 -18
- data/lib/hashie/extensions/dash/coercion.rb +25 -0
- data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
- data/lib/hashie/extensions/dash/property_translation.rb +191 -0
- data/lib/hashie/extensions/deep_fetch.rb +7 -5
- data/lib/hashie/extensions/deep_find.rb +69 -0
- data/lib/hashie/extensions/deep_locate.rb +113 -0
- data/lib/hashie/extensions/deep_merge.rb +35 -12
- data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
- data/lib/hashie/extensions/indifferent_access.rb +28 -16
- data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
- data/lib/hashie/extensions/key_conversion.rb +0 -82
- data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
- data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +18 -0
- data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
- data/lib/hashie/extensions/method_access.rb +154 -11
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
- data/lib/hashie/extensions/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/ruby_version.rb +60 -0
- data/lib/hashie/extensions/ruby_version_check.rb +21 -0
- data/lib/hashie/extensions/strict_key_access.rb +77 -0
- data/lib/hashie/extensions/stringify_keys.rb +71 -0
- data/lib/hashie/extensions/symbolize_keys.rb +71 -0
- data/lib/hashie/hash.rb +27 -8
- data/lib/hashie/logger.rb +18 -0
- data/lib/hashie/mash.rb +235 -57
- data/lib/hashie/railtie.rb +21 -0
- data/lib/hashie/rash.rb +40 -16
- data/lib/hashie/trash.rb +2 -88
- data/lib/hashie/utils.rb +44 -0
- data/lib/hashie/version.rb +1 -1
- metadata +42 -81
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.rubocop.yml +0 -36
- data/.travis.yml +0 -15
- data/Gemfile +0 -11
- data/Guardfile +0 -5
- data/lib/hashie/hash_extensions.rb +0 -47
- data/spec/hashie/clash_spec.rb +0 -48
- data/spec/hashie/dash_spec.rb +0 -338
- data/spec/hashie/extensions/coercion_spec.rb +0 -156
- data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
- data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -121
- data/spec/hashie/hash_spec.rb +0 -66
- data/spec/hashie/mash_spec.rb +0 -467
- data/spec/hashie/rash_spec.rb +0 -44
- data/spec/hashie/trash_spec.rb +0 -193
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/spec.opts +0 -3
- 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
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
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
|
100
|
-
|
101
|
-
|
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
|
-
|
106
|
-
|
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
|
-
|
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
|