hashie 3.4.3 → 5.0.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 +5 -5
- data/CHANGELOG.md +516 -129
- data/CONTRIBUTING.md +24 -7
- data/LICENSE +1 -1
- data/README.md +408 -50
- data/Rakefile +18 -1
- data/UPGRADING.md +157 -7
- data/hashie.gemspec +14 -8
- data/lib/hashie/array.rb +21 -0
- data/lib/hashie/clash.rb +24 -12
- data/lib/hashie/dash.rb +56 -31
- 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 +30 -17
- data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
- data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
- data/lib/hashie/extensions/dash/property_translation.rb +59 -28
- data/lib/hashie/extensions/deep_fetch.rb +5 -3
- data/lib/hashie/extensions/deep_find.rb +14 -5
- data/lib/hashie/extensions/deep_locate.rb +40 -21
- data/lib/hashie/extensions/deep_merge.rb +26 -10
- data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
- data/lib/hashie/extensions/indifferent_access.rb +49 -8
- data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
- 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 +3 -1
- data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
- data/lib/hashie/extensions/method_access.rb +77 -19
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
- 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 +16 -13
- data/lib/hashie/extensions/stringify_keys.rb +1 -1
- data/lib/hashie/extensions/symbolize_keys.rb +13 -2
- data/lib/hashie/hash.rb +18 -11
- data/lib/hashie/logger.rb +18 -0
- data/lib/hashie/mash.rb +177 -43
- data/lib/hashie/railtie.rb +21 -0
- data/lib/hashie/rash.rb +7 -7
- data/lib/hashie/utils.rb +44 -0
- data/lib/hashie/version.rb +1 -1
- data/lib/hashie.rb +33 -17
- metadata +28 -95
- data/spec/hashie/clash_spec.rb +0 -48
- data/spec/hashie/dash_spec.rb +0 -513
- data/spec/hashie/extensions/autoload_spec.rb +0 -24
- data/spec/hashie/extensions/coercion_spec.rb +0 -625
- data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
- data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
- data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
- data/spec/hashie/extensions/deep_find_spec.rb +0 -45
- data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -65
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
- data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
- data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
- data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -184
- data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
- data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
- data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
- data/spec/hashie/hash_spec.rb +0 -84
- data/spec/hashie/mash_spec.rb +0 -680
- data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
- data/spec/hashie/rash_spec.rb +0 -77
- data/spec/hashie/trash_spec.rb +0 -268
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/spec_helper.rb +0 -16
- data/spec/support/module_context.rb +0 -11
@@ -0,0 +1,90 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module Mash
|
4
|
+
module DefineAccessors
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
mod = Ext.new
|
8
|
+
include mod
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.extended(obj)
|
13
|
+
included(obj.singleton_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
class Ext < Module
|
17
|
+
def initialize
|
18
|
+
mod = self
|
19
|
+
define_method(:method_missing) do |method_name, *args, &block|
|
20
|
+
key, suffix = method_name_and_suffix(method_name)
|
21
|
+
case suffix
|
22
|
+
when '='.freeze
|
23
|
+
mod.define_writer(key, method_name)
|
24
|
+
when '?'.freeze
|
25
|
+
mod.define_predicate(key, method_name)
|
26
|
+
when '!'.freeze
|
27
|
+
mod.define_initializing_reader(key, method_name)
|
28
|
+
when '_'.freeze
|
29
|
+
mod.define_underbang_reader(key, method_name)
|
30
|
+
else
|
31
|
+
mod.define_reader(key, method_name)
|
32
|
+
end
|
33
|
+
send(method_name, *args, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_reader(key, method_name)
|
38
|
+
define_method(method_name) do |&block|
|
39
|
+
if key? method_name
|
40
|
+
self.[](method_name, &block)
|
41
|
+
else
|
42
|
+
self.[](key, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_writer(key, method_name)
|
48
|
+
define_method(method_name) do |value = nil|
|
49
|
+
if key? method_name
|
50
|
+
self.[](method_name, &proc)
|
51
|
+
else
|
52
|
+
assign_property(key, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def define_predicate(key, method_name)
|
58
|
+
define_method(method_name) do
|
59
|
+
if key? method_name
|
60
|
+
self.[](method_name, &proc)
|
61
|
+
else
|
62
|
+
!!self[key]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_initializing_reader(key, method_name)
|
68
|
+
define_method(method_name) do
|
69
|
+
if key? method_name
|
70
|
+
self.[](method_name, &proc)
|
71
|
+
else
|
72
|
+
initializing_reader(key)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def define_underbang_reader(key, method_name)
|
78
|
+
define_method(method_name) do
|
79
|
+
if key? method_name
|
80
|
+
self.[](key, &proc)
|
81
|
+
else
|
82
|
+
underbang_reader(key)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module Mash
|
4
|
+
# Overrides the indifferent access of a Mash to keep keys in the
|
5
|
+
# original format given to the Mash.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class KeepingMash < Hashie::Mash
|
9
|
+
# include Hashie::Extensions::Mash::KeepOriginalKeys
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
|
13
|
+
# mash.to_hash #=> { :symbol_key => :symbol, 'string_key' => 'string' }
|
14
|
+
# mash['string_key'] == mash[:string_key] #=> true
|
15
|
+
# mash[:symbol_key] == mash['symbol_key'] #=> true
|
16
|
+
module KeepOriginalKeys
|
17
|
+
def self.included(descendant)
|
18
|
+
error_message = "#{descendant} is not a kind of Hashie::Mash"
|
19
|
+
raise ArgumentError, error_message unless descendant <= Hashie::Mash
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Converts the key when necessary to access the correct Mash key.
|
25
|
+
#
|
26
|
+
# @param [Object, String, Symbol] key the key to access.
|
27
|
+
# @return [Object] the value assigned to the key.
|
28
|
+
def convert_key(key)
|
29
|
+
if regular_key?(key)
|
30
|
+
key
|
31
|
+
elsif (converted_key = __convert(key)) && regular_key?(converted_key)
|
32
|
+
converted_key
|
33
|
+
else
|
34
|
+
key
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Converts symbol/string keys to their alternative formats, but leaves
|
39
|
+
# other keys alone.
|
40
|
+
#
|
41
|
+
# @param [Object, String, Symbol] key the key to convert.
|
42
|
+
# @return [Object, String, Symbol] the converted key.
|
43
|
+
def __convert(key)
|
44
|
+
case key
|
45
|
+
when Symbol then key.to_s
|
46
|
+
when String then key.to_sym
|
47
|
+
else key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -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
|
@@ -3,7 +3,9 @@ module Hashie
|
|
3
3
|
module Mash
|
4
4
|
module SafeAssignment
|
5
5
|
def custom_writer(key, *args) #:nodoc:
|
6
|
-
|
6
|
+
if !key?(key) && respond_to?(key, true)
|
7
|
+
raise ArgumentError, "The property #{key} clashes with an existing method."
|
8
|
+
end
|
7
9
|
super
|
8
10
|
end
|
9
11
|
|
@@ -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::SymbolizeKeys
|
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, if possible
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
# @param [<K>] key the key to attempt convert to a symbol
|
31
|
+
# @return [Symbol, K]
|
32
|
+
def convert_key(key)
|
33
|
+
key.respond_to?(:to_sym) ? key.to_sym : key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -27,7 +27,7 @@ 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
|
@@ -67,7 +67,7 @@ module Hashie
|
|
67
67
|
# h['awesome'] # => 'sauce'
|
68
68
|
#
|
69
69
|
module MethodWriter
|
70
|
-
def
|
70
|
+
def respond_to_missing?(name, include_private = false)
|
71
71
|
return true if name.to_s =~ /=$/
|
72
72
|
super
|
73
73
|
end
|
@@ -106,17 +106,42 @@ module Hashie
|
|
106
106
|
# h.def? # => false
|
107
107
|
# h.hji? # => NoMethodError
|
108
108
|
module MethodQuery
|
109
|
-
def
|
110
|
-
|
111
|
-
|
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
|
112
115
|
end
|
113
116
|
|
114
117
|
def method_missing(name, *args)
|
115
|
-
|
116
|
-
|
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
|
117
129
|
end
|
130
|
+
end
|
118
131
|
|
119
|
-
|
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?('?')
|
120
145
|
end
|
121
146
|
end
|
122
147
|
|
@@ -131,6 +156,22 @@ module Hashie
|
|
131
156
|
end
|
132
157
|
end
|
133
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
|
+
|
134
175
|
# MethodOverridingWriter gives you #key_name= shortcuts for
|
135
176
|
# writing to your hash. It allows methods to be overridden by
|
136
177
|
# #key_name= shortcuts and aliases those methods with two
|
@@ -156,6 +197,8 @@ module Hashie
|
|
156
197
|
# h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
|
157
198
|
#
|
158
199
|
module MethodOverridingWriter
|
200
|
+
include RedefineMethod
|
201
|
+
|
159
202
|
def convert_key(key)
|
160
203
|
key.to_s
|
161
204
|
end
|
@@ -180,16 +223,6 @@ module Hashie
|
|
180
223
|
def already_overridden?(name)
|
181
224
|
method?("__#{name}")
|
182
225
|
end
|
183
|
-
|
184
|
-
def method?(name)
|
185
|
-
methods.map(&:to_s).include?(name)
|
186
|
-
end
|
187
|
-
|
188
|
-
def redefine_method(method_name)
|
189
|
-
eigenclass = class << self; self; end
|
190
|
-
eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
|
191
|
-
eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
|
192
|
-
end
|
193
226
|
end
|
194
227
|
|
195
228
|
# A macro module that will automatically include MethodReader,
|
@@ -200,10 +233,35 @@ module Hashie
|
|
200
233
|
# underscores.
|
201
234
|
module MethodAccessWithOverride
|
202
235
|
def self.included(base)
|
203
|
-
[MethodReader, MethodOverridingWriter,
|
236
|
+
[MethodReader, MethodOverridingWriter,
|
237
|
+
MethodQuery, MethodOverridingInitializer].each do |mod|
|
204
238
|
base.send :include, mod
|
205
239
|
end
|
206
240
|
end
|
207
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
|
208
266
|
end
|
209
267
|
end
|
@@ -1,22 +1,46 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'erb'
|
3
|
+
require 'pathname'
|
4
|
+
|
3
5
|
module Hashie
|
4
6
|
module Extensions
|
5
7
|
module Parsers
|
6
8
|
class YamlErbParser
|
7
|
-
def initialize(file_path)
|
9
|
+
def initialize(file_path, options = {})
|
8
10
|
@content = File.read(file_path)
|
9
|
-
@file_path = file_path
|
11
|
+
@file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
|
12
|
+
@options = options
|
10
13
|
end
|
11
14
|
|
12
15
|
def perform
|
13
16
|
template = ERB.new(@content)
|
14
17
|
template.filename = @file_path
|
15
|
-
|
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)
|
16
23
|
end
|
17
24
|
|
18
|
-
def self.perform(file_path)
|
19
|
-
new(file_path).perform
|
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
|
20
44
|
end
|
21
45
|
end
|
22
46
|
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'hashie/extensions/ruby_version'
|
2
|
+
|
3
|
+
module Hashie
|
4
|
+
module Extensions
|
5
|
+
module RubyVersionCheck
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def with_minimum_ruby(version)
|
12
|
+
yield if with_minimum_ruby?(version)
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_minimum_ruby?(version)
|
16
|
+
RubyVersion.new(RUBY_VERSION) >= RubyVersion.new(version)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Hashie
|
2
2
|
module Extensions
|
3
|
-
# SRP: This extension will fail an error whenever a key is accessed
|
3
|
+
# SRP: This extension will fail an error whenever a key is accessed
|
4
|
+
# that does not exist in the hash.
|
4
5
|
#
|
5
6
|
# EXAMPLE:
|
6
7
|
#
|
@@ -15,12 +16,15 @@ module Hashie
|
|
15
16
|
# >> hash[:cow]
|
16
17
|
# KeyError: key not found: :cow
|
17
18
|
#
|
18
|
-
# NOTE: For googlers coming from Python to Ruby, this extension makes a Hash
|
19
|
+
# NOTE: For googlers coming from Python to Ruby, this extension makes a Hash
|
20
|
+
# behave more like a "Dictionary".
|
19
21
|
#
|
20
22
|
module StrictKeyAccess
|
21
23
|
class DefaultError < StandardError
|
22
|
-
def initialize
|
23
|
-
super
|
24
|
+
def initialize
|
25
|
+
super('Setting or using a default with Hashie::Extensions::StrictKeyAccess'\
|
26
|
+
' does not make sense'
|
27
|
+
)
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
@@ -46,27 +50,26 @@ module Hashie
|
|
46
50
|
end
|
47
51
|
|
48
52
|
def default(_ = nil)
|
49
|
-
|
53
|
+
raise DefaultError
|
50
54
|
end
|
51
55
|
|
52
56
|
def default=(_)
|
53
|
-
|
57
|
+
raise DefaultError
|
54
58
|
end
|
55
59
|
|
56
60
|
def default_proc
|
57
|
-
|
61
|
+
raise DefaultError
|
58
62
|
end
|
59
63
|
|
60
64
|
def default_proc=(_)
|
61
|
-
|
65
|
+
raise DefaultError
|
62
66
|
end
|
63
67
|
|
64
68
|
def key(value)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
result
|
69
|
+
super.tap do |result|
|
70
|
+
if result.nil? && (!key?(result) || self[result] != value)
|
71
|
+
raise KeyError, "key not found with value of #{value.inspect}"
|
72
|
+
end
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
@@ -44,7 +44,7 @@ module Hashie
|
|
44
44
|
# test # => {'abc' => 'def'}
|
45
45
|
def stringify_keys!(hash)
|
46
46
|
hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!)
|
47
|
-
hash.keys.each do |k|
|
47
|
+
hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
|
48
48
|
stringify_keys_recursively!(hash[k])
|
49
49
|
hash[k.to_s] = hash.delete(k)
|
50
50
|
end
|
@@ -44,9 +44,9 @@ module Hashie
|
|
44
44
|
# test # => {:abc => 'def'}
|
45
45
|
def symbolize_keys!(hash)
|
46
46
|
hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!)
|
47
|
-
hash.keys.each do |k|
|
47
|
+
hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
|
48
48
|
symbolize_keys_recursively!(hash[k])
|
49
|
-
hash[k
|
49
|
+
hash[convert_key(k)] = hash.delete(k)
|
50
50
|
end
|
51
51
|
hash
|
52
52
|
end
|
@@ -61,6 +61,17 @@ module Hashie
|
|
61
61
|
symbolize_keys!(new_hash)
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Converts a key to a symbol, if possible
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
# @param [<K>] key the key to attempt convert to a symbol
|
71
|
+
# @return [Symbol, K]
|
72
|
+
def convert_key(key)
|
73
|
+
key.respond_to?(:to_sym) ? key.to_sym : key
|
74
|
+
end
|
64
75
|
end
|
65
76
|
|
66
77
|
class << self
|