sleeping_king_studios-tools 1.2.1 → 1.3.0.rc.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 +4 -4
- data/CHANGELOG.md +99 -0
- data/README.md +29 -1
- data/lib/sleeping_king_studios/tools/array_tools.rb +136 -59
- data/lib/sleeping_king_studios/tools/assertions/aggregator.rb +132 -0
- data/lib/sleeping_king_studios/tools/assertions/messages_strategy.rb +77 -0
- data/lib/sleeping_king_studios/tools/assertions.rb +329 -260
- data/lib/sleeping_king_studios/tools/base.rb +23 -0
- data/lib/sleeping_king_studios/tools/core_tools.rb +49 -18
- data/lib/sleeping_king_studios/tools/hash_tools.rb +109 -9
- data/lib/sleeping_king_studios/tools/integer_tools.rb +1 -1
- data/lib/sleeping_king_studios/tools/messages/registry.rb +149 -0
- data/lib/sleeping_king_studios/tools/messages/strategies/file_strategy.rb +81 -0
- data/lib/sleeping_king_studios/tools/messages/strategies/hash_strategy.rb +72 -0
- data/lib/sleeping_king_studios/tools/messages/strategies.rb +13 -0
- data/lib/sleeping_king_studios/tools/messages/strategy.rb +77 -0
- data/lib/sleeping_king_studios/tools/messages.rb +75 -0
- data/lib/sleeping_king_studios/tools/object_tools.rb +185 -49
- data/lib/sleeping_king_studios/tools/string_tools.rb +20 -5
- data/lib/sleeping_king_studios/tools/toolbelt.rb +76 -30
- data/lib/sleeping_king_studios/tools/toolbox/constant_map.rb +11 -9
- data/lib/sleeping_king_studios/tools/toolbox/inflector/rules.rb +7 -9
- data/lib/sleeping_king_studios/tools/toolbox/initializer.rb +63 -0
- data/lib/sleeping_king_studios/tools/toolbox/mixin.rb +5 -3
- data/lib/sleeping_king_studios/tools/toolbox/semantic_version.rb +11 -11
- data/lib/sleeping_king_studios/tools/toolbox.rb +2 -0
- data/lib/sleeping_king_studios/tools/undefined.rb +50 -0
- data/lib/sleeping_king_studios/tools/version.rb +37 -8
- data/lib/sleeping_king_studios/tools.rb +37 -1
- metadata +14 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddf9748934072d1e7acf8ae748029ed8d616c46fd7d0a502499f0b54bc4e5d1b
|
|
4
|
+
data.tar.gz: bae72b5b57afe8404d45aab67ee59a5e8643b3656820e915c535b934016ee0d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6de4c073168e77a204afc0a1b4cc7e873619fbb9796df24177b7c2125be120df29e22034a690da73f1998822e10667d473bebfcdfb4adcd4da0a6dbea3f796c5
|
|
7
|
+
data.tar.gz: 999e795763737b1f79be6d0884f162cf436d116c7319cd6d66d4934e3bbb2048249bf4d7e9c632379d2d2e5aa01525bf0ddcc52318e5d146153af7971fa945e6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,104 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Tools
|
|
6
|
+
|
|
7
|
+
Tools objects now define a circular `#toolbelt` reference.
|
|
8
|
+
|
|
9
|
+
#### ArrayTools
|
|
10
|
+
|
|
11
|
+
Added `ArrayTools#fetch`, which retrieves the value at the requested index or raises an `IndexError`.
|
|
12
|
+
|
|
13
|
+
Deprecated methods with native equivalents:
|
|
14
|
+
|
|
15
|
+
- Deprecated `#bisect` - use `Enumerable#partition` instead.
|
|
16
|
+
- Deprecated `#count_values` - use `Enumerable#tally` instead.
|
|
17
|
+
- Deprecated `#splice` - use `Array#[]=` with a range instead.
|
|
18
|
+
|
|
19
|
+
#### Assertions
|
|
20
|
+
|
|
21
|
+
Added the following methods to `Assertions`.
|
|
22
|
+
|
|
23
|
+
- `#assert_exclusion`
|
|
24
|
+
- `#assert_inclusion`
|
|
25
|
+
- `#assert_inherits_from` (aliased as `#assert_subclass`)
|
|
26
|
+
- `#validate_exclusion`
|
|
27
|
+
- `#validate_inclusion`
|
|
28
|
+
- `#validate_inherits_from` (aliased as `#validate_subclass`)
|
|
29
|
+
|
|
30
|
+
Updated `Assertions#assert_instance_of` to accept a `Module` as the expected value.
|
|
31
|
+
|
|
32
|
+
Assertions error messages now use `Messages`.
|
|
33
|
+
|
|
34
|
+
- `Assertions#error_message_for` can be called with an unscoped key, e.g. `error_message_for(:blank)`.
|
|
35
|
+
- Error messages can be overwritten by changing the messaging strategy for scope `'sleeping_king_studios.tools.assertions'`.
|
|
36
|
+
|
|
37
|
+
#### CoreTools
|
|
38
|
+
|
|
39
|
+
Updated `CoreTools` deprecations:
|
|
40
|
+
|
|
41
|
+
- Now supports passing `deprecate(message, caller:)` to override displayed backtrace.
|
|
42
|
+
- Initializing `CoreTools` with an invalid deprecation strategy now raises an `ArgumentError`.
|
|
43
|
+
|
|
44
|
+
Deprecated `#require_each` method.
|
|
45
|
+
|
|
46
|
+
#### HashTools
|
|
47
|
+
|
|
48
|
+
Added `HashTools#fetch`, which retrieves the value at the requested key or raises a `KeyError`.
|
|
49
|
+
|
|
50
|
+
#### Messages
|
|
51
|
+
|
|
52
|
+
Added `Messages` tool, which allows generating user-readable messages.
|
|
53
|
+
|
|
54
|
+
- Added `Messages::Registry` for defining scoped message strategies.
|
|
55
|
+
- Added `Messages::Strategy` and subclasses `FileStrategy` and `HashStrategy`.
|
|
56
|
+
|
|
57
|
+
#### ObjectTools
|
|
58
|
+
|
|
59
|
+
Added `#[]` support to `ObjectTools#dig`, allowing access through indexed data structures.
|
|
60
|
+
|
|
61
|
+
- Added `:indifferent_keys` option - if set, treats `String` and `Symbol` values as interchangeable when traversing a `Hash` using `ObjectTools#dig`.
|
|
62
|
+
|
|
63
|
+
Added `ObjectTools#fetch`, which tries to find the requested value via the named method call or by calling `#[]`.
|
|
64
|
+
|
|
65
|
+
Deprecated methods with native equivalents:
|
|
66
|
+
|
|
67
|
+
- Deprecated `#eigenclass` - use `Object#singleton_class` instead.
|
|
68
|
+
- Deprecated `#try` - use the safe access operator `&.` instead.
|
|
69
|
+
|
|
70
|
+
#### StringTools
|
|
71
|
+
|
|
72
|
+
Deprecated methods with native equivalents:
|
|
73
|
+
|
|
74
|
+
- Deprecated `#chain` - use `Object#then {}` instead.
|
|
75
|
+
|
|
76
|
+
Deprecated calling `#pluralize` with multiple arguments.
|
|
77
|
+
|
|
78
|
+
### Toolbelt
|
|
79
|
+
|
|
80
|
+
Implemented `Toolbelt.global`, a thread-safe singleton instance with default configuration.
|
|
81
|
+
|
|
82
|
+
- Calling `Toolbelt.instance` now returns the singleton.
|
|
83
|
+
|
|
84
|
+
Tools objects are now defined lazily.
|
|
85
|
+
|
|
86
|
+
### Toolbox
|
|
87
|
+
|
|
88
|
+
Added keyword support to `ConstantMap.new`.
|
|
89
|
+
|
|
90
|
+
- The value returned by `ConstantMap#to_h` is now immutable.
|
|
91
|
+
|
|
92
|
+
Corrected the scope of `Mixin#included` and `Mixin#prepended` to be `private`.
|
|
93
|
+
|
|
94
|
+
#### Initializer
|
|
95
|
+
|
|
96
|
+
Added `Toolbox::Initializer`, used for defining a one-time initialization for a library or project.
|
|
97
|
+
|
|
98
|
+
### Undefined
|
|
99
|
+
|
|
100
|
+
Added `SleepingKingStudios::Tools::Undefined`, used to represent undefined method parameters.
|
|
101
|
+
|
|
3
102
|
## 1.2.1
|
|
4
103
|
|
|
5
104
|
Added support for Ruby 4.0.
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A library of utility services and concerns to expand the functionality of core c
|
|
|
11
11
|
|
|
12
12
|
## About
|
|
13
13
|
|
|
14
|
-
SleepingKingStudios::Tools is tested against MRI Ruby 3.
|
|
14
|
+
SleepingKingStudios::Tools is tested against MRI Ruby 3.2 through 4.0.
|
|
15
15
|
|
|
16
16
|
### Documentation
|
|
17
17
|
|
|
@@ -30,3 +30,31 @@ The canonical repository for this gem is on [GitHub](https://github.com/sleeping
|
|
|
30
30
|
### Code of Conduct
|
|
31
31
|
|
|
32
32
|
Please note that the `SleepingKingStudios::Tools` project is released with a [Contributor Code of Conduct](https://github.com/sleepingkingstudios/sleeping_king_studios-tools/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms.
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
Add the gem to your `Gemfile` or `gemspec`:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
gem 'sleeping_king_studios-tools'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Require `SleepingKingStudios::Tools` in your code:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
require 'sleeping_king_studios/tools'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
To ensure that [message definitions](./tools/messages) are loaded, call the `SleepingKingStudios::Tools` initializer:
|
|
49
|
+
|
|
50
|
+
- In the [initializer](./initializers) for your project:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
module Space
|
|
54
|
+
@initializer = SleepingKingStudios::Tools::Toolbox::Initializer.new do
|
|
55
|
+
SleepingKingStudios::Tools::Toolbox.initializer.call
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Or, in the entry points of your application (such as a `bin` script or `spec_helper.rb`).
|
|
@@ -4,7 +4,7 @@ require 'sleeping_king_studios/tools'
|
|
|
4
4
|
|
|
5
5
|
module SleepingKingStudios::Tools
|
|
6
6
|
# Tools for working with array-like enumerable objects.
|
|
7
|
-
class ArrayTools < SleepingKingStudios::Tools::Base
|
|
7
|
+
class ArrayTools < SleepingKingStudios::Tools::Base # rubocop:disable Metrics/ClassLength
|
|
8
8
|
# Expected methods that an Array-like object should implement.
|
|
9
9
|
ARRAY_METHODS = %i[[] count each].freeze
|
|
10
10
|
|
|
@@ -18,6 +18,7 @@ module SleepingKingStudios::Tools
|
|
|
18
18
|
:count_values,
|
|
19
19
|
:deep_dup,
|
|
20
20
|
:deep_freeze,
|
|
21
|
+
:fetch,
|
|
21
22
|
:humanize_list,
|
|
22
23
|
:immutable?,
|
|
23
24
|
:mutable?,
|
|
@@ -82,19 +83,19 @@ module SleepingKingStudios::Tools
|
|
|
82
83
|
# #=> [0, 2, 4, 6, 8]
|
|
83
84
|
# rejected
|
|
84
85
|
# #=> [1, 3, 5, 7, 9]
|
|
85
|
-
|
|
86
|
+
#
|
|
87
|
+
# @deprecated v1.3.0 Use Enumerable#partition instead.
|
|
88
|
+
def bisect(ary, &)
|
|
89
|
+
toolbelt.core_tools.deprecate(
|
|
90
|
+
"#{self.class.name}#bisect",
|
|
91
|
+
message: 'Use Enumerable#partition instead.'
|
|
92
|
+
)
|
|
93
|
+
|
|
86
94
|
require_array!(ary)
|
|
87
95
|
|
|
88
96
|
raise ArgumentError, 'no block given' unless block_given?
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
rejected = []
|
|
92
|
-
|
|
93
|
-
ary.each do |item|
|
|
94
|
-
(yield(item) ? selected : rejected) << item
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
[selected, rejected]
|
|
98
|
+
ary.each.partition(&)
|
|
98
99
|
end
|
|
99
100
|
|
|
100
101
|
# Counts the number of times each item or result appears in the object.
|
|
@@ -130,18 +131,21 @@ module SleepingKingStudios::Tools
|
|
|
130
131
|
# @example
|
|
131
132
|
# ArrayTools.count_values([1, 1, 1, 2, 2, 3]) { |i| i ** 2 }
|
|
132
133
|
# #=> { 1 => 3, 4 => 2, 9 => 1 }
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
#
|
|
135
|
+
# @deprecated v1.3.0 Use Enumerable#tally instead.
|
|
136
|
+
def count_values(ary, &)
|
|
137
|
+
toolbelt.core_tools.deprecate(
|
|
138
|
+
"#{self.class.name}#count_values",
|
|
139
|
+
message: 'Use Enumerable#tally instead.'
|
|
140
|
+
)
|
|
135
141
|
|
|
136
|
-
ary
|
|
137
|
-
value = block_given? ? block.call(item) : item
|
|
142
|
+
require_array!(ary)
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
end
|
|
144
|
+
(block_given? ? ary.map(&) : ary.to_a).tally
|
|
141
145
|
end
|
|
142
146
|
alias tally count_values
|
|
143
147
|
|
|
144
|
-
# Creates a deep copy of the
|
|
148
|
+
# Creates a deep copy of the array and its contents.
|
|
145
149
|
#
|
|
146
150
|
# Iterates over the array and returns a new Array with deep copies of each
|
|
147
151
|
# array item.
|
|
@@ -171,7 +175,7 @@ module SleepingKingStudios::Tools
|
|
|
171
175
|
def deep_dup(ary)
|
|
172
176
|
require_array!(ary)
|
|
173
177
|
|
|
174
|
-
ary.map { |obj|
|
|
178
|
+
ary.map { |obj| toolbelt.object_tools.deep_dup obj }
|
|
175
179
|
end
|
|
176
180
|
|
|
177
181
|
# Freezes the array and performs a deep freeze on each array item.
|
|
@@ -197,52 +201,110 @@ module SleepingKingStudios::Tools
|
|
|
197
201
|
|
|
198
202
|
ary.freeze
|
|
199
203
|
|
|
200
|
-
ary.each { |obj|
|
|
204
|
+
ary.each { |obj| toolbelt.object_tools.deep_freeze obj }
|
|
201
205
|
end
|
|
202
206
|
|
|
203
|
-
#
|
|
207
|
+
# @overload fetch(ary, index, default = nil)
|
|
208
|
+
# Retrieves the value at the specified index.
|
|
204
209
|
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
210
|
+
# If the value does not exist, returns the default value, or raises an
|
|
211
|
+
# IndexError if there is no default value. If the object defines a native
|
|
212
|
+
# #fetch method, delegates to the native implementation.
|
|
207
213
|
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
# @option options [String] :last_separator the value to use to separate
|
|
212
|
-
# the final pair of values. Defaults to " and " (note the leading and
|
|
213
|
-
# trailing spaces). Will be combined with the :separator for lists of
|
|
214
|
-
# length 3 or greater.
|
|
215
|
-
# @option options [String] :separator the value to use to separate pairs
|
|
216
|
-
# of values before the last in lists of length 3 or greater. Defaults to
|
|
217
|
-
# ", " (note the trailing space).
|
|
214
|
+
# @param ary [Array] the array or array-like object.
|
|
215
|
+
# @param index [Integer] the index to retrieve.
|
|
216
|
+
# @param default [Object] the default value.
|
|
218
217
|
#
|
|
219
|
-
#
|
|
218
|
+
# @return [Object] the value at the specified index.
|
|
220
219
|
#
|
|
221
|
-
#
|
|
220
|
+
# @raise [IndexError] if the array does not have a value at that index
|
|
221
|
+
# and there is no default value.
|
|
222
222
|
#
|
|
223
|
-
# @
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
223
|
+
# @overload fetch(ary, index, &default)
|
|
224
|
+
# Retrieves the value at the specified index.
|
|
225
|
+
#
|
|
226
|
+
# If the value does not exist, returns the value of the default block, or
|
|
227
|
+
# raises an IndexError if there is no default block. If the object defines
|
|
228
|
+
# a native #fetch method, delegates to the native implementation.
|
|
229
|
+
#
|
|
230
|
+
# @param ary [Array] the array or array-like object.
|
|
231
|
+
# @param index [Integer] the index to retrieve.
|
|
232
|
+
#
|
|
233
|
+
# @yield generates the default value if there is no value at the index.
|
|
234
|
+
#
|
|
235
|
+
# @yieldparam index [Integer] the requested index.
|
|
236
|
+
#
|
|
237
|
+
# @yieldreturn [Object] the default value.
|
|
238
|
+
#
|
|
239
|
+
# @return [Object] the value at the specified index.
|
|
240
|
+
#
|
|
241
|
+
# @raise [IndexError] if the array does not have a value at that index
|
|
242
|
+
# and there is no default value.
|
|
243
|
+
def fetch(ary, index, default = UNDEFINED, &block)
|
|
244
|
+
require_array!(ary)
|
|
245
|
+
|
|
246
|
+
if ary.respond_to?(:fetch)
|
|
247
|
+
return native_fetch(ary, index, default, &block)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
size = ary.respond_to?(:size) ? ary.size : ary.count
|
|
251
|
+
|
|
252
|
+
return ary[index] if index < size && index >= -size
|
|
253
|
+
|
|
254
|
+
return block.call(index) if block_given?
|
|
255
|
+
|
|
256
|
+
return default unless default == UNDEFINED
|
|
257
|
+
|
|
258
|
+
raise IndexError,
|
|
259
|
+
"index #{index} outside of array bounds: -#{size}...#{size}"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# @overload def humanize_list(ary, **options, &)
|
|
263
|
+
# Generates a human-readable string representation of the list items.
|
|
264
|
+
#
|
|
265
|
+
# Accepts a list of values and returns a human-readable string of the
|
|
266
|
+
# values, with the format based on the number of items.
|
|
267
|
+
#
|
|
268
|
+
# @param ary [Array<String>] the list of values to format. Will be
|
|
269
|
+
# coerced to strings using #to_s.
|
|
270
|
+
# @param options [Hash] optional configuration hash.
|
|
271
|
+
#
|
|
272
|
+
# @option options [String] :last_separator the value to use to separate
|
|
273
|
+
# the final pair of values. Defaults to " and " (note the leading and
|
|
274
|
+
# trailing spaces). Will be combined with the :separator for lists of
|
|
275
|
+
# length 3 or greater.
|
|
276
|
+
# @option options [String] :separator the value to use to separate pairs
|
|
277
|
+
# of values before the last in lists of length 3 or greater. Defaults to
|
|
278
|
+
# ", " (note the trailing space).
|
|
279
|
+
#
|
|
280
|
+
# @return [String] the formatted string.
|
|
281
|
+
#
|
|
282
|
+
# @raise [ArgumentError] if the first argument is not an Array-like
|
|
283
|
+
# object.
|
|
284
|
+
#
|
|
285
|
+
# @example With Zero Items
|
|
286
|
+
# ArrayTools.humanize_list([])
|
|
287
|
+
# #=> ''
|
|
288
|
+
#
|
|
289
|
+
# @example With One Item
|
|
290
|
+
# ArrayTools.humanize_list(['spam'])
|
|
291
|
+
# #=> 'spam'
|
|
292
|
+
#
|
|
293
|
+
# @example With Two Items
|
|
294
|
+
# ArrayTools.humanize_list(['spam', 'eggs'])
|
|
295
|
+
# #=> 'spam and eggs'
|
|
296
|
+
#
|
|
297
|
+
# @example With Three Or More Items
|
|
298
|
+
# ArrayTools.humanize_list(['spam', 'eggs', 'bacon', 'spam'])
|
|
299
|
+
# #=> 'spam, eggs, bacon, and spam'
|
|
300
|
+
#
|
|
301
|
+
# @example With Three Or More Items And Options
|
|
302
|
+
# ArrayTools.humanize_list(
|
|
303
|
+
# ['spam', 'eggs', 'bacon', 'spam'],
|
|
304
|
+
# :last_separator => ' or '
|
|
305
|
+
# )
|
|
306
|
+
# #=> 'spam, eggs, bacon, or spam'
|
|
307
|
+
def humanize_list(ary, **, &)
|
|
246
308
|
require_array!(ary)
|
|
247
309
|
|
|
248
310
|
return '' if ary.empty?
|
|
@@ -253,7 +315,7 @@ module SleepingKingStudios::Tools
|
|
|
253
315
|
return ary[0].to_s if size == 1
|
|
254
316
|
|
|
255
317
|
separator, last_separator =
|
|
256
|
-
options_for_humanize_list(size:, **
|
|
318
|
+
options_for_humanize_list(size:, **)
|
|
257
319
|
|
|
258
320
|
return "#{ary[0]}#{last_separator}#{ary[1]}" if size == 2
|
|
259
321
|
|
|
@@ -295,7 +357,9 @@ module SleepingKingStudios::Tools
|
|
|
295
357
|
|
|
296
358
|
return false unless ary.frozen?
|
|
297
359
|
|
|
298
|
-
ary.each
|
|
360
|
+
ary.each do |item|
|
|
361
|
+
return false unless toolbelt.object_tools.immutable?(item)
|
|
362
|
+
end
|
|
299
363
|
|
|
300
364
|
true
|
|
301
365
|
end
|
|
@@ -347,7 +411,14 @@ module SleepingKingStudios::Tools
|
|
|
347
411
|
# #=> ['crossbow']
|
|
348
412
|
# values
|
|
349
413
|
# #=> ['shortbow', 'longbow', 'arbalest', 'chu-ko-nu']
|
|
414
|
+
#
|
|
415
|
+
# @deprecated v1.3.0 Use Array#[]= with a range instead.
|
|
350
416
|
def splice(ary, start, delete_count, *insert)
|
|
417
|
+
toolbelt.core_tools.deprecate(
|
|
418
|
+
"#{self.class.name}#splice",
|
|
419
|
+
message: 'Use Array#[]= with a range instead.'
|
|
420
|
+
)
|
|
421
|
+
|
|
351
422
|
require_array!(ary)
|
|
352
423
|
|
|
353
424
|
start += ary.count if start.negative?
|
|
@@ -361,6 +432,12 @@ module SleepingKingStudios::Tools
|
|
|
361
432
|
|
|
362
433
|
private
|
|
363
434
|
|
|
435
|
+
def native_fetch(ary, index, default, &)
|
|
436
|
+
return ary.fetch(index, &) if default == UNDEFINED
|
|
437
|
+
|
|
438
|
+
ary.fetch(index, default, &)
|
|
439
|
+
end
|
|
440
|
+
|
|
364
441
|
def options_for_humanize_list(
|
|
365
442
|
size:,
|
|
366
443
|
last_separator: ' and ',
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sleeping_king_studios/tools/assertions'
|
|
4
|
+
|
|
5
|
+
module SleepingKingStudios::Tools
|
|
6
|
+
# Utility for grouping multiple assertion statements.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# rocket = Struct.new(:fuel, :launched).new(0.0, true)
|
|
10
|
+
# aggregator = SleepingKingStudios::Tools::Assertions::Aggregator.new
|
|
11
|
+
# aggregator.empty?
|
|
12
|
+
# #=> true
|
|
13
|
+
#
|
|
14
|
+
# aggregator.assert(message: 'is out of fuel') { rocket.fuel > 0 }
|
|
15
|
+
# aggregator.assert(message: 'has already launched') { !rocket.launched }
|
|
16
|
+
# aggregator.empty?
|
|
17
|
+
# #=> false
|
|
18
|
+
# aggregator.failure_message
|
|
19
|
+
# #=> 'is out of fuel, has already launched'
|
|
20
|
+
class Assertions::Aggregator < Assertions
|
|
21
|
+
extend Forwardable
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
super
|
|
25
|
+
|
|
26
|
+
@failures = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @!method <<(message)
|
|
30
|
+
# Appends the message to the failure messages.
|
|
31
|
+
#
|
|
32
|
+
# @param message [String] the message to append.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array] the updated failure messages.
|
|
35
|
+
#
|
|
36
|
+
# @see Array#<<.
|
|
37
|
+
|
|
38
|
+
# @!method clear
|
|
39
|
+
# Removes all items from the failure messages.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array] the empty failure messages.
|
|
42
|
+
#
|
|
43
|
+
# @see Array#clear.
|
|
44
|
+
|
|
45
|
+
# @!method count
|
|
46
|
+
# Returns a count of the failure message.
|
|
47
|
+
#
|
|
48
|
+
# @return [Integer] the number of failure messages.
|
|
49
|
+
#
|
|
50
|
+
# @see Array#count.
|
|
51
|
+
|
|
52
|
+
# @!method each
|
|
53
|
+
# Iterates over the failure messages.
|
|
54
|
+
#
|
|
55
|
+
# @overload each
|
|
56
|
+
# Returns an enumerator that iterates over the failure messages.
|
|
57
|
+
#
|
|
58
|
+
# @return [Enumerator] an enumerator over the messages.
|
|
59
|
+
#
|
|
60
|
+
# @see Enumerable#each.
|
|
61
|
+
#
|
|
62
|
+
# @overload each(&block)
|
|
63
|
+
# Yields each failure message to the block.
|
|
64
|
+
#
|
|
65
|
+
# @yieldparam message [String] the current failure message.
|
|
66
|
+
#
|
|
67
|
+
# @see Enumerable#each.
|
|
68
|
+
|
|
69
|
+
# @!method empty?
|
|
70
|
+
# Checks if there are any failure messages.
|
|
71
|
+
#
|
|
72
|
+
# @return [true, false] true if there are no failure messages; otherwise
|
|
73
|
+
# false.
|
|
74
|
+
#
|
|
75
|
+
# @see Enumerable#empty?
|
|
76
|
+
|
|
77
|
+
# @!method size
|
|
78
|
+
# Returns a count of the failure message.
|
|
79
|
+
#
|
|
80
|
+
# @return [Integer] the number of failure messages.
|
|
81
|
+
#
|
|
82
|
+
# @see Array#size.
|
|
83
|
+
def_delegators :@failures,
|
|
84
|
+
:<<,
|
|
85
|
+
:clear,
|
|
86
|
+
:count,
|
|
87
|
+
:each,
|
|
88
|
+
:empty?,
|
|
89
|
+
:size
|
|
90
|
+
|
|
91
|
+
# (see SleepingKingStudios::Tools::Assertions#assert_group)
|
|
92
|
+
def assert_group(error_class: AssertionError, message: nil, &assertions)
|
|
93
|
+
return super if message
|
|
94
|
+
|
|
95
|
+
raise ArgumentError, 'no block given' unless block_given?
|
|
96
|
+
|
|
97
|
+
assertions.call(self)
|
|
98
|
+
end
|
|
99
|
+
alias aggregate assert_group
|
|
100
|
+
|
|
101
|
+
# Generates a combined failure message from the configured messages.
|
|
102
|
+
#
|
|
103
|
+
# @return [String] the combined messages for each failed assertion.
|
|
104
|
+
#
|
|
105
|
+
# @example With an empty aggregator.
|
|
106
|
+
# aggregator = SleepingKingStudios::Tools::Assertions::Aggregator.new
|
|
107
|
+
#
|
|
108
|
+
# aggregator.failure_message
|
|
109
|
+
# #=> ''
|
|
110
|
+
#
|
|
111
|
+
# @example With an aggregator with failure messages.
|
|
112
|
+
# aggregator = SleepingKingStudios::Tools::Assertions::Aggregator.new
|
|
113
|
+
# aggrgator << 'rocket is out of fuel'
|
|
114
|
+
# aggrgator << 'rocket is not pointed toward space'
|
|
115
|
+
#
|
|
116
|
+
# aggregator.failure_message
|
|
117
|
+
# #=> 'rocket is out of fuel, rocket is not pointed toward space'
|
|
118
|
+
def failure_message
|
|
119
|
+
failures.join(', ')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
attr_reader :failures
|
|
125
|
+
|
|
126
|
+
def handle_error(message:, **_)
|
|
127
|
+
failures << message
|
|
128
|
+
|
|
129
|
+
message
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sleeping_king_studios/tools/assertions'
|
|
4
|
+
|
|
5
|
+
module SleepingKingStudios::Tools
|
|
6
|
+
# Messages strategy for displaying assertions errors.
|
|
7
|
+
#
|
|
8
|
+
# Defines its own internal message templates, and automatically handles value
|
|
9
|
+
# name option (:as option) when generating messages.
|
|
10
|
+
class Assertions::MessagesStrategy < SleepingKingStudios::Tools::Messages::Strategies::HashStrategy
|
|
11
|
+
# rubocop:disable Layout/HashAlignment
|
|
12
|
+
ERROR_MESSAGES =
|
|
13
|
+
{
|
|
14
|
+
'blank' =>
|
|
15
|
+
'must be nil or empty',
|
|
16
|
+
'block' =>
|
|
17
|
+
'block returned a falsy value',
|
|
18
|
+
'boolean' =>
|
|
19
|
+
'must be true or false',
|
|
20
|
+
'class' =>
|
|
21
|
+
'is not a Class',
|
|
22
|
+
'class_or_module' =>
|
|
23
|
+
'is not a Class or Module',
|
|
24
|
+
'exclusion' =>
|
|
25
|
+
'is one of %<expected>s',
|
|
26
|
+
'exclusion_range' =>
|
|
27
|
+
'is within %<range_expr>s',
|
|
28
|
+
'inclusion' =>
|
|
29
|
+
'is not one of %<expected>s',
|
|
30
|
+
'inclusion_range' =>
|
|
31
|
+
'is outside %<range_expr>s',
|
|
32
|
+
'inherit_from' =>
|
|
33
|
+
'does not inherit from %<expected>s',
|
|
34
|
+
'instance_of' =>
|
|
35
|
+
'is not an instance of %<expected>s',
|
|
36
|
+
'instance_of_anonymous' =>
|
|
37
|
+
'is not an instance of %<expected>s (%<parent>s)',
|
|
38
|
+
'matches' =>
|
|
39
|
+
'does not match the expected value',
|
|
40
|
+
'matches_proc' =>
|
|
41
|
+
'does not match the Proc',
|
|
42
|
+
'matches_regexp' =>
|
|
43
|
+
'does not match the pattern %<pattern>s',
|
|
44
|
+
'name' =>
|
|
45
|
+
'is not a String or a Symbol',
|
|
46
|
+
'nil' =>
|
|
47
|
+
'must be nil',
|
|
48
|
+
'not_nil' =>
|
|
49
|
+
'must not be nil',
|
|
50
|
+
# @note: This value will be changed in a future version.
|
|
51
|
+
'presence' =>
|
|
52
|
+
"can't be blank"
|
|
53
|
+
}.freeze
|
|
54
|
+
# rubocop:enable Layout/HashAlignment
|
|
55
|
+
|
|
56
|
+
def initialize
|
|
57
|
+
templates =
|
|
58
|
+
ERROR_MESSAGES
|
|
59
|
+
.transform_keys do |key|
|
|
60
|
+
"sleeping_king_studios.tools.assertions.#{key}"
|
|
61
|
+
end
|
|
62
|
+
.freeze
|
|
63
|
+
|
|
64
|
+
super(templates)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def generate(template, as: nil, parameters: {}, **)
|
|
70
|
+
message = super
|
|
71
|
+
|
|
72
|
+
return message unless as
|
|
73
|
+
|
|
74
|
+
"#{as} #{message}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|