sleeping_king_studios-tools 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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