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.
Files changed (27) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +37 -5
  3. data/DEVELOPMENT.md +5 -7
  4. data/README.md +27 -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 +76 -17
  10. data/lib/sleeping_king_studios/tools/enumerable_tools.rb +6 -3
  11. data/lib/sleeping_king_studios/tools/hash_tools.rb +65 -42
  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 +206 -118
  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 +7 -9
  25. metadata +86 -28
  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,63 +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
12
- define_namespace namespace_name, &block
13
- end # method namespace
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
- 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
+
17
58
  options = {
18
- :allow_nil => allow_nil,
19
- :default => default,
20
- :enum => enum
21
- } # 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
22
66
 
23
- define_accessor option_name, options
24
- define_mutator option_name, options
25
- end # class method option
67
+ option_name.intern
68
+ end
26
69
 
27
70
  private
28
71
 
29
- def define_accessor option_name, options
30
- define_method option_name do
31
- __get_value__(option_name, options)
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
- def define_mutator option_name, options
36
- writer_name = :"#{option_name}="
76
+ define_namespace_accessor(namespace_name, namespace)
37
77
 
38
- define_method writer_name do |value|
39
- __set_value__(option_name, value, options)
40
- end # method option_name=
41
- end # method define_mutator
78
+ namespace
79
+ end
80
+
81
+ def define_namespace_accessor(namespace_name, namespace_class)
82
+ namespace_ivar = :"@#{namespace_name}"
42
83
 
43
- def define_namespace namespace_name, &block
44
- define_method namespace_name do
45
- if instance_variable_defined?(:"@#{namespace_name}")
46
- return instance_variable_get(:"@#{namespace_name}")
47
- end # if
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
- data = __get_value__(namespace_name, :default => Object.new)
50
- config =
51
- SleepingKingStudios::Tools::Toolbox::Configuration.new(data, &block)
91
+ initialize_namespace(namespace_name, namespace_class, &block)
92
+ end
93
+ end
52
94
 
53
- config.__root_namespace__ = __root_namespace__ || self
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
- instance_variable_set(:"@#{namespace_name}", config)
109
+ def guard_abstract_class!
110
+ return unless self == SleepingKingStudios::Tools::Toolbox::Configuration
56
111
 
57
- config
58
- end # method namespace_name
59
- end # method define_namespace
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
- def initialize data = nil
69
- @__data__ = __objectify_data__(data)
70
- @__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
71
127
 
72
- yield(singleton_class) if block_given?
73
- end # constructor
128
+ SleepingKingStudios::Tools::CoreTools
129
+ .deprecate('Configuration', message: 'use a Stannum::Struct')
74
130
 
75
- def [] key
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 # method []
140
+ end
78
141
 
79
- def []= key, value
80
- send(:"#{key}=", value)
81
- end # method []=
142
+ def []=(key, value)
143
+ send(:"#{key}=", value) if respond_to?(key)
144
+ end
82
145
 
83
- def dig *keys
84
- keys.reduce(self) do |hsh, key|
85
- value = hsh[key]
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 # reduce
91
- end # method dig
153
+ end
154
+ end
92
155
 
93
- def fetch key, default = DEFAULT_OPTION
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, 'key not found'
101
- end # method fetch
163
+ raise KeyError, "key not found: #{key.inspect}"
164
+ end
102
165
 
103
166
  protected
104
167
 
105
- attr_accessor :__root_namespace__
168
+ attr_accessor :root_namespace
106
169
 
107
170
  private
108
171
 
109
- attr_reader :__data__
172
+ attr_reader :data
110
173
 
111
- def __blank_value__ value
174
+ def blank_value?(value)
112
175
  value.nil? || (value.respond_to?(:empty?) && value.empty?)
113
- end # method __blank_value__
176
+ end
114
177
 
115
- def __evaluate_default__ default
116
- default.is_a?(Proc) ? __root_namespace__.instance_exec(&default) : default
117
- end # method __evaluate_default__
178
+ def convert_data_to_struct(data)
179
+ return data unless data.is_a?(Hash)
118
180
 
119
- def __get_value__ name, options
120
- default_given = options[:default] != DEFAULT_OPTION
181
+ return Object.new if data.empty?
121
182
 
122
- if __data__.respond_to?(name)
123
- value = __data__.send name
183
+ obj = Struct.new(*data.keys).new
124
184
 
125
- if value.nil? && default_given
126
- value = __evaluate_default__(options[:default])
127
- end # if
185
+ data.each do |key, value|
186
+ val = value.is_a?(Hash) ? convert_data_to_struct(value) : value
128
187
 
129
- __validate_value__ value, options
188
+ obj.send :"#{key}=", val
189
+ end
130
190
 
131
- value
132
- elsif instance_variable_defined?(:"@#{name}")
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
- __validate_value__ value, options
194
+ def evaluate_default(default)
195
+ return default unless default.is_a?(Proc)
139
196
 
140
- value
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
- __validate_value__ nil, options
230
+ validate_value(nil, options)
143
231
 
144
232
  nil
145
- end # if-else
146
- end # method __get_value__
233
+ end
234
+ end
147
235
 
148
- def __objectify_data__ data
149
- 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)
150
239
 
151
- return Object.new if data.empty?
240
+ config.root_namespace = root_namespace || self
152
241
 
153
- obj = Struct.new(*data.keys).new
242
+ instance_variable_set(:"@#{namespace_name}", config)
154
243
 
155
- data.each do |key, value|
156
- val = value.is_a?(Hash) ? __objectify_data__(value) : value
244
+ block.call(config) if block_given?
157
245
 
158
- obj.send :"#{key}=", val
159
- end # each
246
+ config
247
+ end
160
248
 
161
- obj
162
- 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
163
260
 
164
- def __set_value__ name, value, options
261
+ def set_value(name, value, options)
165
262
  writer_name = :"#{name}="
166
263
 
167
- __validate_value__ value, options
264
+ validate_value value, options
168
265
 
169
- if __data__.respond_to?(writer_name)
170
- __data__.send(writer_name, value)
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 # if-else
175
- end # method __set_value__
176
-
177
- def __validate_value__ value, options
178
- return if __blank_value__(value) && options[:allow_nil]
179
-
180
- if options[:enum] && !options[:enum].include?(value)
181
- array_tools = ::SleepingKingStudios::Tools::ArrayTools
182
- valid_options =
183
- array_tools.
184
- humanize_list(
185
- options[:enum].map(&:inspect),
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