sleeping_king_studios-tools 0.7.0.beta.0 → 0.8.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 +37 -5
- data/DEVELOPMENT.md +5 -7
- data/README.md +27 -64
- data/lib/sleeping_king_studios/tools.rb +12 -6
- data/lib/sleeping_king_studios/tools/all.rb +8 -3
- data/lib/sleeping_king_studios/tools/array_tools.rb +83 -58
- data/lib/sleeping_king_studios/tools/base.rb +18 -0
- data/lib/sleeping_king_studios/tools/core_tools.rb +76 -17
- data/lib/sleeping_king_studios/tools/enumerable_tools.rb +6 -3
- data/lib/sleeping_king_studios/tools/hash_tools.rb +65 -42
- data/lib/sleeping_king_studios/tools/integer_tools.rb +97 -55
- data/lib/sleeping_king_studios/tools/object_tools.rb +67 -50
- data/lib/sleeping_king_studios/tools/string_tools.rb +81 -63
- data/lib/sleeping_king_studios/tools/toolbelt.rb +46 -22
- data/lib/sleeping_king_studios/tools/toolbox.rb +2 -2
- data/lib/sleeping_king_studios/tools/toolbox/configuration.rb +206 -118
- data/lib/sleeping_king_studios/tools/toolbox/constant_map.rb +24 -51
- data/lib/sleeping_king_studios/tools/toolbox/delegator.rb +50 -29
- data/lib/sleeping_king_studios/tools/toolbox/inflector.rb +130 -0
- data/lib/sleeping_king_studios/tools/toolbox/inflector/rules.rb +171 -0
- data/lib/sleeping_king_studios/tools/toolbox/mixin.rb +10 -10
- data/lib/sleeping_king_studios/tools/toolbox/semantic_version.rb +15 -14
- data/lib/sleeping_king_studios/tools/version.rb +7 -9
- metadata +86 -28
- data/lib/sleeping_king_studios/tools/semantic_version.rb +0 -15
- data/lib/sleeping_king_studios/tools/string_tools/plural_inflector.rb +0 -185
|
@@ -1,34 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'sleeping_king_studios/tools
|
|
3
|
+
require 'sleeping_king_studios/tools'
|
|
4
4
|
|
|
5
5
|
module SleepingKingStudios::Tools
|
|
6
6
|
# Helper object for quick access to all available tools.
|
|
7
7
|
class Toolbelt < BasicObject
|
|
8
8
|
def self.instance
|
|
9
9
|
@instance ||= new
|
|
10
|
-
end
|
|
10
|
+
end
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
def initialize(deprecation_strategy: nil, inflector: nil)
|
|
13
|
+
@array_tools = ::SleepingKingStudios::Tools::ArrayTools.new
|
|
14
|
+
@core_tools = ::SleepingKingStudios::Tools::CoreTools.new(
|
|
15
|
+
deprecation_strategy: deprecation_strategy
|
|
16
|
+
)
|
|
17
|
+
@hash_tools = ::SleepingKingStudios::Tools::HashTools.new
|
|
18
|
+
@integer_tools = ::SleepingKingStudios::Tools::IntegerTools.new
|
|
19
|
+
@object_tools = ::SleepingKingStudios::Tools::ObjectTools.new
|
|
20
|
+
@string_tools =
|
|
21
|
+
::SleepingKingStudios::Tools::StringTools.new(inflector: inflector)
|
|
22
|
+
end
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
attr_reader :array_tools
|
|
25
|
+
|
|
26
|
+
attr_reader :core_tools
|
|
27
|
+
|
|
28
|
+
attr_reader :hash_tools
|
|
29
|
+
|
|
30
|
+
attr_reader :integer_tools
|
|
31
|
+
|
|
32
|
+
attr_reader :object_tools
|
|
33
|
+
|
|
34
|
+
attr_reader :string_tools
|
|
35
|
+
|
|
36
|
+
alias ary array_tools
|
|
37
|
+
alias hsh hash_tools
|
|
38
|
+
alias int integer_tools
|
|
39
|
+
alias obj object_tools
|
|
40
|
+
alias str string_tools
|
|
41
|
+
|
|
42
|
+
%w[array core hash integer object string].each do |name|
|
|
15
43
|
define_method(name) do
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
44
|
+
::SleepingKingStudios::Tools::CoreTools.deprecate(
|
|
45
|
+
"SleepingKingStudios::Tools::Toolbelt##{name}",
|
|
46
|
+
message: "Use ##{name}_tools instead."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
::SleepingKingStudios::Tools.const_get("#{name.capitalize}Tools")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
23
52
|
|
|
24
53
|
def inspect
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end # string
|
|
31
|
-
end # method inspect
|
|
32
|
-
alias_method :to_s, :inspect
|
|
33
|
-
end # module
|
|
34
|
-
end # module
|
|
54
|
+
"#<#{::Object.instance_method(:class).bind(self).call.name}>"
|
|
55
|
+
end
|
|
56
|
+
alias to_s inspect
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'sleeping_king_studios/tools'
|
|
4
4
|
|
|
@@ -7,4 +7,4 @@ module SleepingKingStudios::Tools
|
|
|
7
7
|
# are larger than or do not fit the functional paradigm of the tools.*
|
|
8
8
|
# pattern.
|
|
9
9
|
module Toolbox; end
|
|
10
|
-
end
|
|
10
|
+
end
|
|
@@ -1,63 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'sleeping_king_studios/tools/core_tools'
|
|
3
4
|
require 'sleeping_king_studios/tools/toolbox'
|
|
4
5
|
|
|
5
6
|
module SleepingKingStudios::Tools::Toolbox
|
|
6
|
-
class
|
|
7
|
+
# Abstract base class for defining configuration objects.
|
|
8
|
+
class Configuration # rubocop:disable Metrics/ClassLength
|
|
9
|
+
# Class methods for configuration objects.
|
|
7
10
|
module ClassMethods
|
|
8
11
|
DEFAULT_OPTION = Object.new.freeze
|
|
9
12
|
|
|
10
13
|
# Defines a nested namespace for the configuration object.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
#
|
|
15
|
+
# A namespace is represented by a nested configuration object, which has
|
|
16
|
+
# its own options and namespaces.
|
|
17
|
+
#
|
|
18
|
+
# @param namespace_name [String] The name of the namespace.
|
|
19
|
+
#
|
|
20
|
+
# @yield namespace If a block is given, that block will be executed in the
|
|
21
|
+
# context of the newly created namespace.
|
|
22
|
+
#
|
|
23
|
+
# @return [Configuration] the created namespace object.
|
|
24
|
+
def namespace(namespace_name, &block)
|
|
25
|
+
guard_abstract_class!
|
|
26
|
+
|
|
27
|
+
namespace =
|
|
28
|
+
(@namespaces ||= {}).fetch(namespace_name) do
|
|
29
|
+
@namespaces[namespace_name] = define_namespace namespace_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
namespace.instance_exec(namespace, &block) if block_given?
|
|
33
|
+
|
|
34
|
+
namespace
|
|
35
|
+
end
|
|
14
36
|
|
|
15
37
|
# Defines an option for the configuration object.
|
|
16
|
-
|
|
38
|
+
#
|
|
39
|
+
# A configuration option has a name and a value. It can be defined with a
|
|
40
|
+
# default value, or to allow or prohibit nil values or restrict possible
|
|
41
|
+
# values to a given set.
|
|
42
|
+
#
|
|
43
|
+
# @param option_name [String] The name of the option.
|
|
44
|
+
# @param allow_nil [true, false] If false, setting the option value to nil
|
|
45
|
+
# or an empty value will raise an error, as will trying to access the
|
|
46
|
+
# value when it has not been set. Defaults to false.
|
|
47
|
+
# @param default [Object] The default value for the option. If this is not
|
|
48
|
+
# set, the default value for the option will be nil.
|
|
49
|
+
# @param enum [Array] An enumerable list of valid values for the option.
|
|
50
|
+
def option(
|
|
51
|
+
option_name,
|
|
52
|
+
allow_nil: false,
|
|
53
|
+
default: DEFAULT_OPTION,
|
|
54
|
+
enum: nil
|
|
55
|
+
)
|
|
56
|
+
guard_abstract_class!
|
|
57
|
+
|
|
17
58
|
options = {
|
|
18
|
-
:
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
}
|
|
59
|
+
allow_nil: allow_nil,
|
|
60
|
+
default: default,
|
|
61
|
+
enum: enum
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
define_option_accessor option_name, options
|
|
65
|
+
define_option_mutator option_name, options
|
|
22
66
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end # class method option
|
|
67
|
+
option_name.intern
|
|
68
|
+
end
|
|
26
69
|
|
|
27
70
|
private
|
|
28
71
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end # method option_name
|
|
33
|
-
end # method define_accessor
|
|
72
|
+
def define_namespace(namespace_name)
|
|
73
|
+
namespace =
|
|
74
|
+
Class.new(SleepingKingStudios::Tools::Toolbox::Configuration)
|
|
34
75
|
|
|
35
|
-
|
|
36
|
-
writer_name = :"#{option_name}="
|
|
76
|
+
define_namespace_accessor(namespace_name, namespace)
|
|
37
77
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
namespace
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def define_namespace_accessor(namespace_name, namespace_class)
|
|
82
|
+
namespace_ivar = :"@#{namespace_name}"
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
84
|
+
define_method namespace_name do |&block|
|
|
85
|
+
if instance_variable_defined?(namespace_ivar)
|
|
86
|
+
return instance_variable_get(namespace_ivar).tap do |config|
|
|
87
|
+
block&.call(config)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
48
90
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
91
|
+
initialize_namespace(namespace_name, namespace_class, &block)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
52
94
|
|
|
53
|
-
|
|
95
|
+
def define_option_accessor(option_name, options)
|
|
96
|
+
define_method option_name do
|
|
97
|
+
get_value(option_name, options)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def define_option_mutator(option_name, options)
|
|
102
|
+
writer_name = :"#{option_name}="
|
|
103
|
+
|
|
104
|
+
define_method writer_name do |value|
|
|
105
|
+
set_value(option_name, value, options)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
54
108
|
|
|
55
|
-
|
|
109
|
+
def guard_abstract_class!
|
|
110
|
+
return unless self == SleepingKingStudios::Tools::Toolbox::Configuration
|
|
56
111
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end # module
|
|
112
|
+
raise "can't define namespace or option on abstract class"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
61
115
|
extend ClassMethods
|
|
62
116
|
|
|
63
117
|
DEFAULT_OPTION = ClassMethods::DEFAULT_OPTION
|
|
@@ -65,130 +119,164 @@ module SleepingKingStudios::Tools::Toolbox
|
|
|
65
119
|
# @param data [Hash, Object] The data source used to populate configuration
|
|
66
120
|
# values. Can be a Hash or a data object. If the data source is nil, or no
|
|
67
121
|
# data source is given, values will be set to their respective defaults.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
#
|
|
123
|
+
# @yieldparam [Class] The singleton class of the new configuration object.
|
|
124
|
+
def initialize(data = nil)
|
|
125
|
+
@data = convert_data_to_struct(data)
|
|
126
|
+
@root_namespace = self
|
|
71
127
|
|
|
72
|
-
|
|
73
|
-
|
|
128
|
+
SleepingKingStudios::Tools::CoreTools
|
|
129
|
+
.deprecate('Configuration', message: 'use a Stannum::Struct')
|
|
74
130
|
|
|
75
|
-
|
|
131
|
+
return unless block_given?
|
|
132
|
+
|
|
133
|
+
# :nocov:
|
|
134
|
+
yield(singleton_class)
|
|
135
|
+
# :nocov:
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def [](key)
|
|
76
139
|
send(key) if respond_to?(key)
|
|
77
|
-
end
|
|
140
|
+
end
|
|
78
141
|
|
|
79
|
-
def []=
|
|
80
|
-
send(:"#{key}=", value)
|
|
81
|
-
end
|
|
142
|
+
def []=(key, value)
|
|
143
|
+
send(:"#{key}=", value) if respond_to?(key)
|
|
144
|
+
end
|
|
82
145
|
|
|
83
|
-
def dig
|
|
84
|
-
keys.reduce(self) do |
|
|
85
|
-
value =
|
|
146
|
+
def dig(*keys)
|
|
147
|
+
keys.reduce(self) do |config, key|
|
|
148
|
+
value = config[key]
|
|
86
149
|
|
|
87
150
|
return value if value.nil?
|
|
88
151
|
|
|
89
152
|
value
|
|
90
|
-
end
|
|
91
|
-
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
92
155
|
|
|
93
|
-
def fetch
|
|
156
|
+
def fetch(key, default = DEFAULT_OPTION)
|
|
94
157
|
return send(key) if respond_to?(key)
|
|
95
158
|
|
|
96
159
|
return default unless default == DEFAULT_OPTION
|
|
97
160
|
|
|
98
|
-
return yield if block_given?
|
|
161
|
+
return yield(key) if block_given?
|
|
99
162
|
|
|
100
|
-
raise KeyError,
|
|
101
|
-
end
|
|
163
|
+
raise KeyError, "key not found: #{key.inspect}"
|
|
164
|
+
end
|
|
102
165
|
|
|
103
166
|
protected
|
|
104
167
|
|
|
105
|
-
attr_accessor :
|
|
168
|
+
attr_accessor :root_namespace
|
|
106
169
|
|
|
107
170
|
private
|
|
108
171
|
|
|
109
|
-
attr_reader :
|
|
172
|
+
attr_reader :data
|
|
110
173
|
|
|
111
|
-
def
|
|
174
|
+
def blank_value?(value)
|
|
112
175
|
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
113
|
-
end
|
|
176
|
+
end
|
|
114
177
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
end # method __evaluate_default__
|
|
178
|
+
def convert_data_to_struct(data)
|
|
179
|
+
return data unless data.is_a?(Hash)
|
|
118
180
|
|
|
119
|
-
|
|
120
|
-
default_given = options[:default] != DEFAULT_OPTION
|
|
181
|
+
return Object.new if data.empty?
|
|
121
182
|
|
|
122
|
-
|
|
123
|
-
value = __data__.send name
|
|
183
|
+
obj = Struct.new(*data.keys).new
|
|
124
184
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end # if
|
|
185
|
+
data.each do |key, value|
|
|
186
|
+
val = value.is_a?(Hash) ? convert_data_to_struct(value) : value
|
|
128
187
|
|
|
129
|
-
|
|
188
|
+
obj.send :"#{key}=", val
|
|
189
|
+
end
|
|
130
190
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Recall values locally if data source is immutable.
|
|
134
|
-
return instance_variable_get(:"@#{name}")
|
|
135
|
-
elsif default_given
|
|
136
|
-
value = __evaluate_default__(options[:default])
|
|
191
|
+
obj
|
|
192
|
+
end
|
|
137
193
|
|
|
138
|
-
|
|
194
|
+
def evaluate_default(default)
|
|
195
|
+
return default unless default.is_a?(Proc)
|
|
139
196
|
|
|
140
|
-
|
|
197
|
+
root_namespace.instance_exec(&default)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def get_default_value(options)
|
|
201
|
+
value = evaluate_default(options[:default])
|
|
202
|
+
|
|
203
|
+
validate_value value, options
|
|
204
|
+
|
|
205
|
+
value
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def get_method_value(name, options)
|
|
209
|
+
value = data.send(name)
|
|
210
|
+
|
|
211
|
+
# :nocov:
|
|
212
|
+
if value.nil? && options[:default] != DEFAULT_OPTION
|
|
213
|
+
value = evaluate_default(options[:default])
|
|
214
|
+
end
|
|
215
|
+
# :nocov:
|
|
216
|
+
|
|
217
|
+
validate_value(value, options)
|
|
218
|
+
|
|
219
|
+
value
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def get_value(name, options)
|
|
223
|
+
if data.respond_to?(name)
|
|
224
|
+
get_method_value(name, options)
|
|
225
|
+
elsif instance_variable_defined?(:"@#{name}")
|
|
226
|
+
instance_variable_get(:"@#{name}")
|
|
227
|
+
elsif options[:default] != DEFAULT_OPTION
|
|
228
|
+
get_default_value(options)
|
|
141
229
|
else
|
|
142
|
-
|
|
230
|
+
validate_value(nil, options)
|
|
143
231
|
|
|
144
232
|
nil
|
|
145
|
-
end
|
|
146
|
-
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
147
235
|
|
|
148
|
-
def
|
|
149
|
-
|
|
236
|
+
def initialize_namespace(namespace_name, namespace_class, &block)
|
|
237
|
+
data = get_value(namespace_name, default: Object.new)
|
|
238
|
+
config = namespace_class.new(data)
|
|
150
239
|
|
|
151
|
-
|
|
240
|
+
config.root_namespace = root_namespace || self
|
|
152
241
|
|
|
153
|
-
|
|
242
|
+
instance_variable_set(:"@#{namespace_name}", config)
|
|
154
243
|
|
|
155
|
-
|
|
156
|
-
val = value.is_a?(Hash) ? __objectify_data__(value) : value
|
|
244
|
+
block.call(config) if block_given?
|
|
157
245
|
|
|
158
|
-
|
|
159
|
-
|
|
246
|
+
config
|
|
247
|
+
end
|
|
160
248
|
|
|
161
|
-
|
|
162
|
-
|
|
249
|
+
def invalid_value_message(value, options)
|
|
250
|
+
array_tools = ::SleepingKingStudios::Tools::ArrayTools
|
|
251
|
+
valid_options =
|
|
252
|
+
array_tools
|
|
253
|
+
.humanize_list(
|
|
254
|
+
options[:enum].map(&:inspect),
|
|
255
|
+
last_separator: ' or '
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
"expected option to be #{valid_options}, but was #{value.inspect}"
|
|
259
|
+
end
|
|
163
260
|
|
|
164
|
-
def
|
|
261
|
+
def set_value(name, value, options)
|
|
165
262
|
writer_name = :"#{name}="
|
|
166
263
|
|
|
167
|
-
|
|
264
|
+
validate_value value, options
|
|
168
265
|
|
|
169
|
-
if
|
|
170
|
-
|
|
266
|
+
if data.respond_to?(writer_name)
|
|
267
|
+
data.send(writer_name, value)
|
|
171
268
|
else
|
|
172
269
|
# Store values locally if data source is immutable.
|
|
173
270
|
instance_variable_set(:"@#{name}", value)
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def
|
|
178
|
-
return if
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
:last_separator => ' or '
|
|
187
|
-
) # end humanize_list
|
|
188
|
-
|
|
189
|
-
raise RuntimeError,
|
|
190
|
-
"expected option to be #{valid_options}, but was #{value.inspect}"
|
|
191
|
-
end # if
|
|
192
|
-
end # method __validate_value__
|
|
193
|
-
end # class
|
|
194
|
-
end # module
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def validate_value(value, options)
|
|
275
|
+
return if blank_value?(value) && options[:allow_nil]
|
|
276
|
+
|
|
277
|
+
return unless options[:enum] && !options[:enum].include?(value)
|
|
278
|
+
|
|
279
|
+
raise invalid_value_message(value, options)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|