sleeping_king_studios-tools 0.7.1 → 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 +32 -3
- data/DEVELOPMENT.md +5 -7
- data/README.md +3 -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 +68 -22
- data/lib/sleeping_king_studios/tools/enumerable_tools.rb +6 -3
- data/lib/sleeping_king_studios/tools/hash_tools.rb +59 -47
- 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 +197 -122
- 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 +6 -8
- metadata +84 -26
- 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,76 +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
|
-
|
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
|
+
|
12
27
|
namespace =
|
13
28
|
(@namespaces ||= {}).fetch(namespace_name) do
|
14
29
|
@namespaces[namespace_name] = define_namespace namespace_name
|
15
|
-
end
|
30
|
+
end
|
16
31
|
|
17
|
-
namespace.instance_exec
|
32
|
+
namespace.instance_exec(namespace, &block) if block_given?
|
18
33
|
|
19
34
|
namespace
|
20
|
-
end
|
35
|
+
end
|
21
36
|
|
22
37
|
# Defines an option for the configuration object.
|
23
|
-
|
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
|
+
|
24
58
|
options = {
|
25
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
}
|
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
|
29
66
|
|
30
|
-
|
31
|
-
|
32
|
-
end # class method option
|
67
|
+
option_name.intern
|
68
|
+
end
|
33
69
|
|
34
70
|
private
|
35
71
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
end # method option_name
|
40
|
-
end # method define_accessor
|
72
|
+
def define_namespace(namespace_name)
|
73
|
+
namespace =
|
74
|
+
Class.new(SleepingKingStudios::Tools::Toolbox::Configuration)
|
41
75
|
|
42
|
-
|
43
|
-
writer_name = :"#{option_name}="
|
76
|
+
define_namespace_accessor(namespace_name, namespace)
|
44
77
|
|
45
|
-
|
46
|
-
|
47
|
-
end # method option_name=
|
48
|
-
end # method define_mutator
|
78
|
+
namespace
|
79
|
+
end
|
49
80
|
|
50
|
-
def
|
51
|
-
|
52
|
-
Class.new(SleepingKingStudios::Tools::Toolbox::Configuration)
|
81
|
+
def define_namespace_accessor(namespace_name, namespace_class)
|
82
|
+
namespace_ivar = :"@#{namespace_name}"
|
53
83
|
|
54
84
|
define_method namespace_name do |&block|
|
55
|
-
if instance_variable_defined?(
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
60
90
|
|
61
|
-
|
91
|
+
initialize_namespace(namespace_name, namespace_class, &block)
|
92
|
+
end
|
93
|
+
end
|
62
94
|
|
63
|
-
|
64
|
-
|
95
|
+
def define_option_accessor(option_name, options)
|
96
|
+
define_method option_name do
|
97
|
+
get_value(option_name, options)
|
98
|
+
end
|
99
|
+
end
|
65
100
|
|
66
|
-
|
101
|
+
def define_option_mutator(option_name, options)
|
102
|
+
writer_name = :"#{option_name}="
|
67
103
|
|
68
|
-
|
69
|
-
|
104
|
+
define_method writer_name do |value|
|
105
|
+
set_value(option_name, value, options)
|
106
|
+
end
|
107
|
+
end
|
70
108
|
|
71
|
-
|
72
|
-
|
73
|
-
|
109
|
+
def guard_abstract_class!
|
110
|
+
return unless self == SleepingKingStudios::Tools::Toolbox::Configuration
|
111
|
+
|
112
|
+
raise "can't define namespace or option on abstract class"
|
113
|
+
end
|
114
|
+
end
|
74
115
|
extend ClassMethods
|
75
116
|
|
76
117
|
DEFAULT_OPTION = ClassMethods::DEFAULT_OPTION
|
@@ -78,130 +119,164 @@ module SleepingKingStudios::Tools::Toolbox
|
|
78
119
|
# @param data [Hash, Object] The data source used to populate configuration
|
79
120
|
# values. Can be a Hash or a data object. If the data source is nil, or no
|
80
121
|
# data source is given, values will be set to their respective defaults.
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
127
|
+
|
128
|
+
SleepingKingStudios::Tools::CoreTools
|
129
|
+
.deprecate('Configuration', message: 'use a Stannum::Struct')
|
130
|
+
|
131
|
+
return unless block_given?
|
84
132
|
|
85
|
-
|
86
|
-
|
133
|
+
# :nocov:
|
134
|
+
yield(singleton_class)
|
135
|
+
# :nocov:
|
136
|
+
end
|
87
137
|
|
88
|
-
def []
|
138
|
+
def [](key)
|
89
139
|
send(key) if respond_to?(key)
|
90
|
-
end
|
140
|
+
end
|
91
141
|
|
92
|
-
def []=
|
93
|
-
send(:"#{key}=", value)
|
94
|
-
end
|
142
|
+
def []=(key, value)
|
143
|
+
send(:"#{key}=", value) if respond_to?(key)
|
144
|
+
end
|
95
145
|
|
96
|
-
def dig
|
97
|
-
keys.reduce(self) do |
|
98
|
-
value =
|
146
|
+
def dig(*keys)
|
147
|
+
keys.reduce(self) do |config, key|
|
148
|
+
value = config[key]
|
99
149
|
|
100
150
|
return value if value.nil?
|
101
151
|
|
102
152
|
value
|
103
|
-
end
|
104
|
-
end
|
153
|
+
end
|
154
|
+
end
|
105
155
|
|
106
|
-
def fetch
|
156
|
+
def fetch(key, default = DEFAULT_OPTION)
|
107
157
|
return send(key) if respond_to?(key)
|
108
158
|
|
109
159
|
return default unless default == DEFAULT_OPTION
|
110
160
|
|
111
|
-
return yield if block_given?
|
161
|
+
return yield(key) if block_given?
|
112
162
|
|
113
|
-
raise KeyError,
|
114
|
-
end
|
163
|
+
raise KeyError, "key not found: #{key.inspect}"
|
164
|
+
end
|
115
165
|
|
116
166
|
protected
|
117
167
|
|
118
|
-
attr_accessor :
|
168
|
+
attr_accessor :root_namespace
|
119
169
|
|
120
170
|
private
|
121
171
|
|
122
|
-
attr_reader :
|
172
|
+
attr_reader :data
|
123
173
|
|
124
|
-
def
|
174
|
+
def blank_value?(value)
|
125
175
|
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
126
|
-
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def convert_data_to_struct(data)
|
179
|
+
return data unless data.is_a?(Hash)
|
180
|
+
|
181
|
+
return Object.new if data.empty?
|
182
|
+
|
183
|
+
obj = Struct.new(*data.keys).new
|
127
184
|
|
128
|
-
|
129
|
-
|
130
|
-
end # method __evaluate_default__
|
185
|
+
data.each do |key, value|
|
186
|
+
val = value.is_a?(Hash) ? convert_data_to_struct(value) : value
|
131
187
|
|
132
|
-
|
133
|
-
|
188
|
+
obj.send :"#{key}=", val
|
189
|
+
end
|
134
190
|
|
135
|
-
|
136
|
-
|
191
|
+
obj
|
192
|
+
end
|
137
193
|
|
138
|
-
|
139
|
-
|
140
|
-
end # if
|
194
|
+
def evaluate_default(default)
|
195
|
+
return default unless default.is_a?(Proc)
|
141
196
|
|
142
|
-
|
197
|
+
root_namespace.instance_exec(&default)
|
198
|
+
end
|
143
199
|
|
144
|
-
|
145
|
-
|
146
|
-
# Recall values locally if data source is immutable.
|
147
|
-
return instance_variable_get(:"@#{name}")
|
148
|
-
elsif default_given
|
149
|
-
value = __evaluate_default__(options[:default])
|
200
|
+
def get_default_value(options)
|
201
|
+
value = evaluate_default(options[:default])
|
150
202
|
|
151
|
-
|
203
|
+
validate_value value, options
|
152
204
|
|
153
|
-
|
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)
|
154
229
|
else
|
155
|
-
|
230
|
+
validate_value(nil, options)
|
156
231
|
|
157
232
|
nil
|
158
|
-
end
|
159
|
-
end
|
233
|
+
end
|
234
|
+
end
|
160
235
|
|
161
|
-
def
|
162
|
-
|
236
|
+
def initialize_namespace(namespace_name, namespace_class, &block)
|
237
|
+
data = get_value(namespace_name, default: Object.new)
|
238
|
+
config = namespace_class.new(data)
|
163
239
|
|
164
|
-
|
240
|
+
config.root_namespace = root_namespace || self
|
165
241
|
|
166
|
-
|
242
|
+
instance_variable_set(:"@#{namespace_name}", config)
|
167
243
|
|
168
|
-
|
169
|
-
val = value.is_a?(Hash) ? __objectify_data__(value) : value
|
244
|
+
block.call(config) if block_given?
|
170
245
|
|
171
|
-
|
172
|
-
|
246
|
+
config
|
247
|
+
end
|
173
248
|
|
174
|
-
|
175
|
-
|
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
|
176
260
|
|
177
|
-
def
|
261
|
+
def set_value(name, value, options)
|
178
262
|
writer_name = :"#{name}="
|
179
263
|
|
180
|
-
|
264
|
+
validate_value value, options
|
181
265
|
|
182
|
-
if
|
183
|
-
|
266
|
+
if data.respond_to?(writer_name)
|
267
|
+
data.send(writer_name, value)
|
184
268
|
else
|
185
269
|
# Store values locally if data source is immutable.
|
186
270
|
instance_variable_set(:"@#{name}", value)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def
|
191
|
-
return if
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
:last_separator => ' or '
|
200
|
-
) # end humanize_list
|
201
|
-
|
202
|
-
raise RuntimeError,
|
203
|
-
"expected option to be #{valid_options}, but was #{value.inspect}"
|
204
|
-
end # if
|
205
|
-
end # method __validate_value__
|
206
|
-
end # class
|
207
|
-
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
|