nummy 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +383 -0
- data/lib/nummy/auto_sequenceable.rb +163 -0
- data/lib/nummy/const_enumerable.rb +100 -0
- data/lib/nummy/enum.rb +391 -0
- data/lib/nummy/errors.rb +7 -0
- data/lib/nummy/member_enumerable.rb +87 -0
- data/lib/nummy/ordered_const_enumerable.rb +81 -0
- data/lib/nummy/version.rb +6 -0
- data/lib/nummy.rb +22 -0
- metadata +274 -0
data/lib/nummy/enum.rb
ADDED
@@ -0,0 +1,391 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nummy/auto_sequenceable"
|
4
|
+
require "nummy/ordered_const_enumerable"
|
5
|
+
require "nummy/errors"
|
6
|
+
|
7
|
+
module Nummy
|
8
|
+
# An opinionated class for defining enum-like data using constants.
|
9
|
+
#
|
10
|
+
# There are many gems out there for defining enum-like data, including
|
11
|
+
# +ActiveRecord::Enum+, +Ruby::Enum+, and +Dry::Types::Enum+. The defining
|
12
|
+
# characteristic of this particular class is that it is meant to be light on
|
13
|
+
# metaprogramming in order to make it easier for static analysis tools to work
|
14
|
+
# with the enum fields. By basing the enums on constants, tools like Ruby LSP
|
15
|
+
# can provide better support for things like "Go To Definition",
|
16
|
+
# autocompletion of enum keys, and documentation for specific enum fields on
|
17
|
+
# hover.
|
18
|
+
#
|
19
|
+
# The enums are built on top of {AutoSequenceable}, which provides support for
|
20
|
+
# defining sequences of values, and {OrderedConstEnumerable} to provide support for
|
21
|
+
#
|
22
|
+
# This class is designed to be used as a superclass that can be inherited from
|
23
|
+
# to create classes that define the enum fields.
|
24
|
+
#
|
25
|
+
# @see AutoSequenceable
|
26
|
+
# @see OrderedConstEnumerable
|
27
|
+
#
|
28
|
+
# @example Compass points
|
29
|
+
# class CardinalDirection < Nummy::Enum
|
30
|
+
# NORTH = 0
|
31
|
+
# EAST = 90
|
32
|
+
# SOUTH = 180
|
33
|
+
# WEST = 270
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# CompassPoint::NORTH
|
37
|
+
# # => 0
|
38
|
+
#
|
39
|
+
# CompassPoint.values
|
40
|
+
# # => [0, 90, 180, 270]
|
41
|
+
#
|
42
|
+
# CompassPoint.key?(:NORTH)
|
43
|
+
# # => true
|
44
|
+
#
|
45
|
+
# CompassPoint.key?(:NORTH_NORTH_WEST)
|
46
|
+
# # => false
|
47
|
+
#
|
48
|
+
# @example Using as a guard
|
49
|
+
# class Status < Nummy::Enum
|
50
|
+
# DRAFT = :draft
|
51
|
+
# PUBLISHED = :published
|
52
|
+
# RETRACTED = :retracted
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def update_status(status)
|
56
|
+
# raise ArgumentError, "invalid status" unless Status.any?(status)
|
57
|
+
# end
|
58
|
+
class Enum
|
59
|
+
class << self
|
60
|
+
include AutoSequenceable
|
61
|
+
include OrderedConstEnumerable
|
62
|
+
|
63
|
+
# Anonymous block forwarding doesn't work when the block is passed within
|
64
|
+
# another block, which is what we do in +.enum+ below.
|
65
|
+
#
|
66
|
+
# rubocop:disable Naming/BlockForwarding
|
67
|
+
|
68
|
+
# Creates a new subclass of +Nummy::Enum+ using the given name-value pairs from
|
69
|
+
# the keyword arguments.
|
70
|
+
#
|
71
|
+
# @overload define(*auto_keys, **pairs)
|
72
|
+
# @param auto_keys [Array<Symbol>] keys that will be assigned a value using {Enum.auto}
|
73
|
+
# @param pairs [Hash{Symbol => Object}] keys that will be assigned the given values
|
74
|
+
# @return [Class<Nummy::Enum>]
|
75
|
+
#
|
76
|
+
# @overload define(*auto_keys, **pairs, &)
|
77
|
+
# @param auto_keys [Array<Symbol>] keys that will be assigned a value using {Enum.auto}
|
78
|
+
# @param pairs [Hash{Symbol => Object}] keys that will be assigned the given values
|
79
|
+
# @yield [Class<Nummy::Enum>] the new subclass
|
80
|
+
# @return [Class<Nummy::Enum>]
|
81
|
+
def define(*auto_keys, **pairs, &block)
|
82
|
+
Class.new(self) do
|
83
|
+
auto_keys.each { |key| const_set(key, auto) }
|
84
|
+
pairs.each { |key, value| const_set(key, value) }
|
85
|
+
|
86
|
+
class_eval(&block) if block_given?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
# rubocop:enable Naming/BlockForwarding
|
90
|
+
|
91
|
+
# Prevent inheriting from subclasses of {Enum} in order to keep the
|
92
|
+
# constant lookup logic simple.
|
93
|
+
#
|
94
|
+
# Without inheritance, we only need to lookup constants on +self+. With
|
95
|
+
# inheritance, we would have to look up constants on +self+ and all of its
|
96
|
+
# superclasses, and we would also need to keep track of ordering.
|
97
|
+
#
|
98
|
+
# For the use case of enums, inheritance should not be neccessary. New
|
99
|
+
# enums can be combined using other methods, like {#merge}.
|
100
|
+
#
|
101
|
+
# @private
|
102
|
+
def inherited(subclass)
|
103
|
+
if subclass.superclass != Enum
|
104
|
+
raise InheritanceError, "cannot subclass enum #{display_name}"
|
105
|
+
end
|
106
|
+
|
107
|
+
super(subclass)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns whether the value of any constant in +self+ is +==+ to +other+
|
111
|
+
#
|
112
|
+
# This is intended to allow the enum to be used in case expressions,
|
113
|
+
#
|
114
|
+
# @param [Object] other
|
115
|
+
# @return [Boolean]
|
116
|
+
#
|
117
|
+
# @example Checking if a value matches a value in +self+.
|
118
|
+
# class Status < Nummy::Enum
|
119
|
+
# DRAFT = 'draft'
|
120
|
+
# PUBLISHED = 'published'
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# puts Status === 'draft'
|
124
|
+
# # => true
|
125
|
+
#
|
126
|
+
# puts Status === 'retracted'
|
127
|
+
# # => false
|
128
|
+
#
|
129
|
+
# @example Matching a value in a case expression
|
130
|
+
# class SuccessStatus < Nummy::Enum
|
131
|
+
# OK = 200
|
132
|
+
# CREATED = 201
|
133
|
+
# # ...
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# class RedirectStatus < Nummy::Enum
|
137
|
+
# MULTIPLE_CHOICES = 300
|
138
|
+
# MOVED_PERMANENTLY = 301
|
139
|
+
# # ...
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# case status
|
143
|
+
# when SucessStatus
|
144
|
+
# # ...
|
145
|
+
# when RedirectStatus
|
146
|
+
# # ...
|
147
|
+
# end
|
148
|
+
def ===(other)
|
149
|
+
each_value.any? { |value| value == other }
|
150
|
+
end
|
151
|
+
|
152
|
+
# @!method each_const_name
|
153
|
+
# Alias for {OrderedConstEnumerable#each_const_name}.
|
154
|
+
alias each_key each_const_name
|
155
|
+
|
156
|
+
# @!method each_const_value
|
157
|
+
# Alias for {OrderedConstEnumerable#each_const_value}.
|
158
|
+
#
|
159
|
+
# @note
|
160
|
+
# This method is used as the basis for the enumeration methods provided
|
161
|
+
# by the +Enumerable+ module.
|
162
|
+
alias each_value each_const_value
|
163
|
+
alias each each_const_value
|
164
|
+
|
165
|
+
# @!method each_const_pair
|
166
|
+
# Alias for {OrderedConstEnumerable#each_const_pair}.
|
167
|
+
alias each_pair each_const_pair
|
168
|
+
|
169
|
+
# Returns a new +Array+ containing the keys in +self+.
|
170
|
+
#
|
171
|
+
# The keys will be the same as the names of the own (i.e. non-inherited)
|
172
|
+
# constants in +self+, ordered following the logic in
|
173
|
+
# {OrderedConstEnumerable}.
|
174
|
+
#
|
175
|
+
# @return [Array<Symbol>]
|
176
|
+
def keys
|
177
|
+
# Not using each_key.to_a because each_key just calls nummy_constants
|
178
|
+
# internally, so we can skip converting the constants to an enumerator
|
179
|
+
# and just get the relevant constants directly.
|
180
|
+
nummy_constants
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the key of the constant in +self+ that has the given value.
|
184
|
+
#
|
185
|
+
# If no key has the given value, returns +nil+.
|
186
|
+
#
|
187
|
+
# @param target_value [Object]
|
188
|
+
# @return [Symbol, NilClass]
|
189
|
+
#
|
190
|
+
# @raise [KeyError] if no key exists.
|
191
|
+
def key(target_value)
|
192
|
+
each_pair { |key, value| return key if value == target_value }
|
193
|
+
|
194
|
+
# stree-ignore
|
195
|
+
raise KeyError.new("no key found for value: #{target_value.inspect}", receiver: self)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns whether +self+ has the given key.
|
199
|
+
#
|
200
|
+
# @param [Symbol, String] key
|
201
|
+
# @return [Boolean]
|
202
|
+
def key?(key)
|
203
|
+
return false if key_scoped?(key)
|
204
|
+
const_defined?(key, false)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns the value of the constant in +self+ that has the given key.
|
208
|
+
#
|
209
|
+
# @raise [KeyError] if the given key is invalid or does not exist.
|
210
|
+
def value(key)
|
211
|
+
if key_scoped?(key)
|
212
|
+
# stree-ignore
|
213
|
+
raise KeyError.new("cannot use scoped enum key: #{key.inspect}", receiver: self, key:)
|
214
|
+
end
|
215
|
+
|
216
|
+
nummy_const_get(key)
|
217
|
+
rescue NameError
|
218
|
+
# Avoid attaching the NameError as the cause, because it just adds
|
219
|
+
# unnecessary noise in the stack trace.
|
220
|
+
|
221
|
+
# stree-ignore
|
222
|
+
raise KeyError.new("key not found: #{key.inspect}", receiver: self, key:), cause: nil
|
223
|
+
end
|
224
|
+
|
225
|
+
# Allow bracketed lookup of constants by key.
|
226
|
+
alias [] value
|
227
|
+
|
228
|
+
# Returns whether +self+ has a constant with the given value.
|
229
|
+
#
|
230
|
+
# @param target_value [Object]
|
231
|
+
# @return [Boolean]
|
232
|
+
def value?(target_value)
|
233
|
+
each_value.any? { |value| value == target_value }
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns an +Array+ containing the values of the constants in +self+.
|
237
|
+
#
|
238
|
+
# @return [Array<Object>]
|
239
|
+
def values
|
240
|
+
each_value.to_a
|
241
|
+
end
|
242
|
+
|
243
|
+
# Returns an +Array+ containing the values of the given keys in +self+.
|
244
|
+
#
|
245
|
+
# @param [Array<Symbol, String>] selected_keys
|
246
|
+
# @return [Array<Object>]
|
247
|
+
# @raise [KeyError] if any of the given names are invalid or do not exist.
|
248
|
+
def values_at(*selected_keys)
|
249
|
+
selected_keys.map! { |key| value(key) }
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns an +Array+ containing the key-value pairs in +self+.
|
253
|
+
#
|
254
|
+
# @return [Array<Array<Symbol, Object>>]
|
255
|
+
def pairs
|
256
|
+
each_pair.to_a
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns the value for the constant with the given key, with ability to
|
260
|
+
# return default values if no constant is defined.
|
261
|
+
#
|
262
|
+
# @overload fetch(key)
|
263
|
+
# Returns the value for the constant with the given key.
|
264
|
+
#
|
265
|
+
# @raise [KeyError] if no such constant exists.
|
266
|
+
# @return [Object]
|
267
|
+
#
|
268
|
+
# @overload fetch(key, default_value)
|
269
|
+
# Returns the value for the constant with the given key. If no such
|
270
|
+
# constant exists, returns the given default value.
|
271
|
+
#
|
272
|
+
# @return [Object]
|
273
|
+
#
|
274
|
+
# @overload fetch(key, &)
|
275
|
+
# Returns the value for the constant with the given key. If no such
|
276
|
+
# constant exists, calls the given block and returns its result.
|
277
|
+
#
|
278
|
+
# @yieldparam [Symbol] key
|
279
|
+
# @yieldreturn [Object]
|
280
|
+
# @return [Object]
|
281
|
+
def fetch(key, *args)
|
282
|
+
case args.size
|
283
|
+
when 1
|
284
|
+
warn "block supersedes default value argument" if block_given?
|
285
|
+
when (2..)
|
286
|
+
# stree-ignore
|
287
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1..2)"
|
288
|
+
end
|
289
|
+
|
290
|
+
value(key)
|
291
|
+
rescue KeyError
|
292
|
+
if block_given?
|
293
|
+
yield key
|
294
|
+
elsif args.any?
|
295
|
+
args.first
|
296
|
+
else
|
297
|
+
raise
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Creates a new sublcass of {Enum} by merging the entries of +self+ with
|
302
|
+
# the entries of the other enums.
|
303
|
+
#
|
304
|
+
# Internally, this method converts each of the arguments to hashes then
|
305
|
+
# merges them together, so the merging of duplicate keys follows the
|
306
|
+
# behavior of +Hash#merge+.
|
307
|
+
def merge(*other_enums, &)
|
308
|
+
return self if other_enums.empty?
|
309
|
+
|
310
|
+
to_h
|
311
|
+
.merge(*other_enums.map!(&:to_h), &)
|
312
|
+
.each_with_object(Class.new(Enum)) do |(key, value), merged|
|
313
|
+
merged.const_set(key, value)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Creates a new sublcass of {Enum} consisting of the entries of +self+
|
318
|
+
# that correspond to the given keys.
|
319
|
+
#
|
320
|
+
# @param [Array<Symbol>] selected_keys
|
321
|
+
# @return [Class<Enum>]
|
322
|
+
#
|
323
|
+
# @raise [KeyError] if any of the given keys do not exist in +self+.
|
324
|
+
def slice(*selected_keys)
|
325
|
+
selected_keys.each_with_object(Class.new(Enum)) do |key, sliced|
|
326
|
+
sliced.const_set(key, self[key])
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Returns whether +self+ has any pairs.
|
331
|
+
#
|
332
|
+
# @return [Boolean]
|
333
|
+
def empty?
|
334
|
+
size.zero?
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns the count of the constants in +self+.
|
338
|
+
#
|
339
|
+
# @return [Integer]
|
340
|
+
def size
|
341
|
+
keys.size
|
342
|
+
end
|
343
|
+
|
344
|
+
alias length size
|
345
|
+
|
346
|
+
# Converts the enum to a Hash, where the constant names as keys and the
|
347
|
+
# constant values as values.
|
348
|
+
#
|
349
|
+
# @return [Hash{Symbol => Object}]
|
350
|
+
def to_h
|
351
|
+
each_pair.to_h
|
352
|
+
end
|
353
|
+
|
354
|
+
# Alias to support splatting enums into keyword args.
|
355
|
+
alias to_hash to_h
|
356
|
+
|
357
|
+
# Returns a string representation of +self+.
|
358
|
+
#
|
359
|
+
# The string will contain the name-value pairs of the constants in +self+.
|
360
|
+
#
|
361
|
+
# @return [String]
|
362
|
+
def inspect
|
363
|
+
parts = [display_name]
|
364
|
+
each_pair { |key, value| parts << "#{key}=#{value.inspect}" }
|
365
|
+
|
366
|
+
"#<#{parts.join(" ")}>"
|
367
|
+
end
|
368
|
+
|
369
|
+
# Implements +PP+ support for +self+.
|
370
|
+
def pretty_print(pp)
|
371
|
+
pp.group(1, "#<#{display_name}", ">") do
|
372
|
+
each_pair do |key, value|
|
373
|
+
pp.breakable
|
374
|
+
pp.text "#{key}="
|
375
|
+
pp.pp value
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
private
|
381
|
+
|
382
|
+
def display_name
|
383
|
+
name || Nummy::Enum.name
|
384
|
+
end
|
385
|
+
|
386
|
+
def key_scoped?(key)
|
387
|
+
key.to_s.include?(":")
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
data/lib/nummy/errors.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nummy
|
4
|
+
# A mixin that makes a class +Enumerable+ using its +members+ method.
|
5
|
+
#
|
6
|
+
# This can be used for things like iterating over the members of a +Data+
|
7
|
+
# class without having to write the boilerplate code to do so.
|
8
|
+
#
|
9
|
+
# This aims to implement small set of methods, modeled after +Struct+, rather
|
10
|
+
# than a more fully-featured set of methods like +Hash+.
|
11
|
+
module MemberEnumerable
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
# Iterates through the values of the members in +self+.
|
15
|
+
#
|
16
|
+
# The order of the pairs is the same as the order of the array returned by
|
17
|
+
# calling +members+.
|
18
|
+
#
|
19
|
+
# @overload each(&)
|
20
|
+
# Calls the given block with the value of each member in +self+.
|
21
|
+
#
|
22
|
+
# @yieldparam [Object] value
|
23
|
+
# @return [self]
|
24
|
+
#
|
25
|
+
# @overload each
|
26
|
+
# Returns an +Enumerator+ that calls the given block with the value of
|
27
|
+
# each member in +self+.
|
28
|
+
#
|
29
|
+
# @return [Enumerator<Object>]
|
30
|
+
def each(&)
|
31
|
+
return enum_for unless block_given?
|
32
|
+
|
33
|
+
members.each { |member| yield send(member) }
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Iterates through the name-value pairs of the members in +self+.
|
38
|
+
#
|
39
|
+
# The order of the pairs is the same as the order of the array returned by
|
40
|
+
# calling +members+.
|
41
|
+
#
|
42
|
+
# @overload each_pair(&)
|
43
|
+
# Calls the given block with a two-item array containing the name and
|
44
|
+
# value of each member in +self+.
|
45
|
+
#
|
46
|
+
# @yieldparam [Symbol] name
|
47
|
+
# @yieldparam [Object] value
|
48
|
+
# @return [self]
|
49
|
+
#
|
50
|
+
# @overload each_pair
|
51
|
+
# Returns an +Enumerator+ that iterates over with a two-item array
|
52
|
+
# containing the name and value of each member in +self+.
|
53
|
+
# @return [Enumerator<Array<Symbol, Object>>]
|
54
|
+
def each_pair
|
55
|
+
return enum_for(__method__) unless block_given?
|
56
|
+
|
57
|
+
members.each { |member| yield member, send(member) }
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns an +Array+ containing the values in +self+.
|
62
|
+
#
|
63
|
+
# @return [Array<Object>]
|
64
|
+
def values
|
65
|
+
deconstruct
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns an +Array+ containing the values of the given members in +self+.
|
69
|
+
#
|
70
|
+
# @param [Array<Symbol, String>] selected_members
|
71
|
+
# @return [Array<Object>]
|
72
|
+
#
|
73
|
+
# @raise [NoMethodError] if any of the given members are invalid or do not exist.
|
74
|
+
def values_at(*selected_members)
|
75
|
+
selected_members.map! { |member| send(member) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the count of members in +self+.
|
79
|
+
#
|
80
|
+
# @return [Integer]
|
81
|
+
def size
|
82
|
+
members.size
|
83
|
+
end
|
84
|
+
|
85
|
+
alias length size
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nummy/const_enumerable"
|
4
|
+
|
5
|
+
module Nummy
|
6
|
+
# A module that can be extended in order to enumerate over constants
|
7
|
+
# in a +Hash+-like manner. This can be used to build enum-like classes and
|
8
|
+
# modules that work nicely with static analysis.
|
9
|
+
#
|
10
|
+
# The enumeration order for methods defined by this module is guaranteed to be
|
11
|
+
# stable across calls and methods, and in _most_ cases will match the order in
|
12
|
+
# which the constants were defined.
|
13
|
+
#
|
14
|
+
# The only exception is for constants that are defined *before* extending
|
15
|
+
# {ConstEnumerable}. Those constants are sorted in stable way, but the order
|
16
|
+
# will not necessarily match insertion order. This is because those constants
|
17
|
+
# are looked up using +Module#constants+ when this module is extended, and
|
18
|
+
# that method does not have any ordering guarantees. Instead, these constants
|
19
|
+
# are sorted using +Symbol#<=>+.
|
20
|
+
#
|
21
|
+
# @note
|
22
|
+
# If stable ordering is not important, consider using {ConstEnumerable},
|
23
|
+
# which is more performant but does not guarantee ordering.
|
24
|
+
#
|
25
|
+
# @see ConstEnumerable
|
26
|
+
# @see Enum
|
27
|
+
module OrderedConstEnumerable
|
28
|
+
include ConstEnumerable
|
29
|
+
|
30
|
+
# @!method each_const_name(...)
|
31
|
+
# Same as {ConstEnumerable#each_const_name}, but with stable ordering.
|
32
|
+
|
33
|
+
# @!method each_const_value(...)
|
34
|
+
# Same as {ConstEnumerable#each_const_value}, but with stable ordering.
|
35
|
+
|
36
|
+
# @!method each_const_pair(...)
|
37
|
+
# Same as {ConstEnumerable#each_const_pair}, but with stable ordering.
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def self.extended(mod)
|
41
|
+
# Sort own constants lexicographically to ensure a stable order, because
|
42
|
+
# +Module#constants+ does not have its own ordering guarantees.
|
43
|
+
sorted_constants = mod.send(:constants, false).sort!
|
44
|
+
|
45
|
+
sorted_constants.each do |name|
|
46
|
+
mod.send(:nummy_record_const_insertion_order, name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Hook used to track the order in which constants are added to the group.
|
51
|
+
#
|
52
|
+
# We do this because +Module#constants+ does not guarantee the order that
|
53
|
+
# constants will be returned in.
|
54
|
+
#
|
55
|
+
# @note
|
56
|
+
# This hook was added in Ruby 3.2.
|
57
|
+
#
|
58
|
+
# @private
|
59
|
+
def const_added(name)
|
60
|
+
super(name)
|
61
|
+
nummy_record_const_insertion_order(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def nummy_constants
|
67
|
+
nummy_const_insertion_orders.keys & constants(false)
|
68
|
+
end
|
69
|
+
|
70
|
+
def nummy_const_insertion_orders
|
71
|
+
# using a Hash to get the deduplication benefits of a Set while also
|
72
|
+
# preserving the insertion order. We only care about the keys, so the
|
73
|
+
# values will all be ignored.
|
74
|
+
@nummy_const_insertion_orders ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def nummy_record_const_insertion_order(name)
|
78
|
+
nummy_const_insertion_orders[name] = nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/nummy.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nummy/errors"
|
4
|
+
require "nummy/version"
|
5
|
+
|
6
|
+
# Utilities that that build on Ruby's Enumerable module to provide
|
7
|
+
# functionality like enumerated types ("enums"), enumerating over constants,
|
8
|
+
# and enumerating over the members of data classes.
|
9
|
+
#
|
10
|
+
# This module does NOT provide additional methods to the Enumerable module.
|
11
|
+
module Nummy
|
12
|
+
autoload :AutoSequenceable, "nummy/auto_sequenceable"
|
13
|
+
autoload :ConstEnumerable, "nummy/const_enumerable"
|
14
|
+
autoload :OrderedConstEnumerable, "nummy/ordered_const_enumerable"
|
15
|
+
autoload :MemberEnumerable, "nummy/member_enumerable"
|
16
|
+
autoload :Enum, "nummy/enum"
|
17
|
+
|
18
|
+
# Alias for {Enum.define}
|
19
|
+
def self.enum(...)
|
20
|
+
Enum.define(...)
|
21
|
+
end
|
22
|
+
end
|