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.
Files changed (27) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +32 -3
  3. data/DEVELOPMENT.md +5 -7
  4. data/README.md +3 -64
  5. data/lib/sleeping_king_studios/tools.rb +12 -6
  6. data/lib/sleeping_king_studios/tools/all.rb +8 -3
  7. data/lib/sleeping_king_studios/tools/array_tools.rb +83 -58
  8. data/lib/sleeping_king_studios/tools/base.rb +18 -0
  9. data/lib/sleeping_king_studios/tools/core_tools.rb +68 -22
  10. data/lib/sleeping_king_studios/tools/enumerable_tools.rb +6 -3
  11. data/lib/sleeping_king_studios/tools/hash_tools.rb +59 -47
  12. data/lib/sleeping_king_studios/tools/integer_tools.rb +97 -55
  13. data/lib/sleeping_king_studios/tools/object_tools.rb +67 -50
  14. data/lib/sleeping_king_studios/tools/string_tools.rb +81 -63
  15. data/lib/sleeping_king_studios/tools/toolbelt.rb +46 -22
  16. data/lib/sleeping_king_studios/tools/toolbox.rb +2 -2
  17. data/lib/sleeping_king_studios/tools/toolbox/configuration.rb +197 -122
  18. data/lib/sleeping_king_studios/tools/toolbox/constant_map.rb +24 -51
  19. data/lib/sleeping_king_studios/tools/toolbox/delegator.rb +50 -29
  20. data/lib/sleeping_king_studios/tools/toolbox/inflector.rb +130 -0
  21. data/lib/sleeping_king_studios/tools/toolbox/inflector/rules.rb +171 -0
  22. data/lib/sleeping_king_studios/tools/toolbox/mixin.rb +10 -10
  23. data/lib/sleeping_king_studios/tools/toolbox/semantic_version.rb +15 -14
  24. data/lib/sleeping_king_studios/tools/version.rb +6 -8
  25. metadata +84 -26
  26. data/lib/sleeping_king_studios/tools/semantic_version.rb +0 -15
  27. data/lib/sleeping_king_studios/tools/string_tools/plural_inflector.rb +0 -185
@@ -1,34 +1,58 @@
1
- # lib/sleeping_king_studios/tools/toolbelt.rb
1
+ # frozen_string_literal: true
2
2
 
3
- require 'sleeping_king_studios/tools/all'
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 # class method instance
10
+ end
11
11
 
12
- namespace = ::SleepingKingStudios::Tools
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
- %w(array core hash integer object string).each do |name|
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
- begin
17
- namespace.const_get("#{name.capitalize}Tools")
18
- rescue NameError => exception
19
- nil
20
- end # begin-rescue
21
- end # each
22
- end # each
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
- @to_s ||=
26
- begin
27
- object_class = class << self; self; end.superclass
28
-
29
- "#<#{object_class.name}>"
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
- # lib/sleeping_king_studios/tools/toolbox.rb
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 # module
10
+ end
@@ -1,76 +1,117 @@
1
- # lib/sleeping_king_studios/tools/toolbox/configuration.rb
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 Configuration
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
- def namespace namespace_name, &block
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 # fetch
30
+ end
16
31
 
17
- namespace.instance_exec namespace, &block if block_given?
32
+ namespace.instance_exec(namespace, &block) if block_given?
18
33
 
19
34
  namespace
20
- end # method namespace
35
+ end
21
36
 
22
37
  # Defines an option for the configuration object.
23
- def option option_name, allow_nil: false, default: DEFAULT_OPTION, enum: nil
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
- :allow_nil => allow_nil,
26
- :default => default,
27
- :enum => enum
28
- } # end hash
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
- define_accessor option_name, options
31
- define_mutator option_name, options
32
- end # class method option
67
+ option_name.intern
68
+ end
33
69
 
34
70
  private
35
71
 
36
- def define_accessor option_name, options
37
- define_method option_name do
38
- __get_value__(option_name, options)
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
- def define_mutator option_name, options
43
- writer_name = :"#{option_name}="
76
+ define_namespace_accessor(namespace_name, namespace)
44
77
 
45
- define_method writer_name do |value|
46
- __set_value__(option_name, value, options)
47
- end # method option_name=
48
- end # method define_mutator
78
+ namespace
79
+ end
49
80
 
50
- def define_namespace namespace_name
51
- namespace =
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?(:"@#{namespace_name}")
56
- config = instance_variable_get(:"@#{namespace_name}")
57
- else
58
- data = __get_value__(namespace_name, :default => Object.new)
59
- config = namespace.new(data)
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
- config.__root_namespace__ = __root_namespace__ || self
91
+ initialize_namespace(namespace_name, namespace_class, &block)
92
+ end
93
+ end
62
94
 
63
- instance_variable_set(:"@#{namespace_name}", config)
64
- end # if
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
- block.call(config) if block
101
+ def define_option_mutator(option_name, options)
102
+ writer_name = :"#{option_name}="
67
103
 
68
- config
69
- end # method namespace_name
104
+ define_method writer_name do |value|
105
+ set_value(option_name, value, options)
106
+ end
107
+ end
70
108
 
71
- namespace
72
- end # method define_namespace
73
- end # module
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
- def initialize data = nil
82
- @__data__ = __objectify_data__(data)
83
- @__root_namespace__ = self
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
- yield(singleton_class) if block_given?
86
- end # constructor
133
+ # :nocov:
134
+ yield(singleton_class)
135
+ # :nocov:
136
+ end
87
137
 
88
- def [] key
138
+ def [](key)
89
139
  send(key) if respond_to?(key)
90
- end # method []
140
+ end
91
141
 
92
- def []= key, value
93
- send(:"#{key}=", value)
94
- end # method []=
142
+ def []=(key, value)
143
+ send(:"#{key}=", value) if respond_to?(key)
144
+ end
95
145
 
96
- def dig *keys
97
- keys.reduce(self) do |hsh, key|
98
- value = hsh[key]
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 # reduce
104
- end # method dig
153
+ end
154
+ end
105
155
 
106
- def fetch key, default = DEFAULT_OPTION
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, 'key not found'
114
- end # method fetch
163
+ raise KeyError, "key not found: #{key.inspect}"
164
+ end
115
165
 
116
166
  protected
117
167
 
118
- attr_accessor :__root_namespace__
168
+ attr_accessor :root_namespace
119
169
 
120
170
  private
121
171
 
122
- attr_reader :__data__
172
+ attr_reader :data
123
173
 
124
- def __blank_value__ value
174
+ def blank_value?(value)
125
175
  value.nil? || (value.respond_to?(:empty?) && value.empty?)
126
- end # method __blank_value__
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
- def __evaluate_default__ default
129
- default.is_a?(Proc) ? __root_namespace__.instance_exec(&default) : default
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
- def __get_value__ name, options
133
- default_given = options[:default] != DEFAULT_OPTION
188
+ obj.send :"#{key}=", val
189
+ end
134
190
 
135
- if __data__.respond_to?(name)
136
- value = __data__.send name
191
+ obj
192
+ end
137
193
 
138
- if value.nil? && default_given
139
- value = __evaluate_default__(options[:default])
140
- end # if
194
+ def evaluate_default(default)
195
+ return default unless default.is_a?(Proc)
141
196
 
142
- __validate_value__ value, options
197
+ root_namespace.instance_exec(&default)
198
+ end
143
199
 
144
- value
145
- elsif instance_variable_defined?(:"@#{name}")
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
- __validate_value__ value, options
203
+ validate_value value, options
152
204
 
153
- value
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
- __validate_value__ nil, options
230
+ validate_value(nil, options)
156
231
 
157
232
  nil
158
- end # if-else
159
- end # method __get_value__
233
+ end
234
+ end
160
235
 
161
- def __objectify_data__ data
162
- return data unless data.is_a?(Hash)
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
- return Object.new if data.empty?
240
+ config.root_namespace = root_namespace || self
165
241
 
166
- obj = Struct.new(*data.keys).new
242
+ instance_variable_set(:"@#{namespace_name}", config)
167
243
 
168
- data.each do |key, value|
169
- val = value.is_a?(Hash) ? __objectify_data__(value) : value
244
+ block.call(config) if block_given?
170
245
 
171
- obj.send :"#{key}=", val
172
- end # each
246
+ config
247
+ end
173
248
 
174
- obj
175
- end # method __objectify_data__
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 __set_value__ name, value, options
261
+ def set_value(name, value, options)
178
262
  writer_name = :"#{name}="
179
263
 
180
- __validate_value__ value, options
264
+ validate_value value, options
181
265
 
182
- if __data__.respond_to?(writer_name)
183
- __data__.send(writer_name, value)
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 # if-else
188
- end # method __set_value__
189
-
190
- def __validate_value__ value, options
191
- return if __blank_value__(value) && options[:allow_nil]
192
-
193
- if options[:enum] && !options[:enum].include?(value)
194
- array_tools = ::SleepingKingStudios::Tools::ArrayTools
195
- valid_options =
196
- array_tools.
197
- humanize_list(
198
- options[:enum].map(&:inspect),
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