typesafe_enum 0.2.2 → 0.3.1
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/.github/workflows/build.yml +30 -0
- data/.gitignore +209 -29
- data/.idea/.rakeTasks +7 -0
- data/.idea/codeStyles/Project.xml +45 -0
- data/.idea/codeStyles/codeStyleConfig.xml +5 -0
- data/.idea/go.imports.xml +6 -0
- data/.idea/inspectionProfiles/Project_Default.xml +18 -0
- data/.idea/misc.xml +9 -0
- data/.idea/modules.xml +8 -0
- data/.idea/typesafe_enum.iml +78 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +184 -22
- data/.ruby-version +1 -1
- data/.travis.yml +7 -0
- data/CHANGES.md +17 -0
- data/README.md +15 -1
- data/Rakefile +8 -42
- data/lib/typesafe_enum/base.rb +18 -98
- data/lib/typesafe_enum/class_methods.rb +167 -0
- data/lib/typesafe_enum/exceptions.rb +7 -0
- data/lib/typesafe_enum/module_info.rb +8 -6
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +2 -0
- data/spec/.rubocop.yml +3 -0
- data/spec/unit/typesafe_enum/base_spec.rb +199 -7
- data/typesafe_enum.gemspec +9 -5
- metadata +57 -19
data/.rubocop.yml
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
# Disable compact style check for example.rb
|
|
2
|
-
Style/ClassAndModuleChildren:
|
|
3
|
-
Exclude:
|
|
4
|
-
- 'example.rb'
|
|
5
|
-
|
|
6
1
|
# Allow one line around block body (Layout/EmptyLines will still disallow two or more)
|
|
7
2
|
Layout/EmptyLinesAroundBlockBody:
|
|
8
3
|
Enabled: false
|
|
@@ -27,11 +22,6 @@ Layout/MultilineMethodCallIndentation:
|
|
|
27
22
|
Layout/MultilineOperationIndentation:
|
|
28
23
|
Enabled: false
|
|
29
24
|
|
|
30
|
-
# Just because something looks like an accessor doesn't mean it is one
|
|
31
|
-
Naming/PredicateName:
|
|
32
|
-
Exclude:
|
|
33
|
-
- 'app/controllers/application_controller.rb'
|
|
34
|
-
|
|
35
25
|
# Confusing and weird
|
|
36
26
|
Naming/VariableNumber:
|
|
37
27
|
Enabled: False
|
|
@@ -60,18 +50,6 @@ Style/CommentedKeyword:
|
|
|
60
50
|
Style/Documentation:
|
|
61
51
|
Enabled: false
|
|
62
52
|
|
|
63
|
-
# Added in RuboCop 0.80
|
|
64
|
-
Style/HashEachMethods:
|
|
65
|
-
Enabled: true
|
|
66
|
-
|
|
67
|
-
# Added in RuboCop 0.80
|
|
68
|
-
Style/HashTransformKeys:
|
|
69
|
-
Enabled: true
|
|
70
|
-
|
|
71
|
-
# Added in RuboCop 0.80
|
|
72
|
-
Style/HashTransformValues:
|
|
73
|
-
Enabled: true
|
|
74
|
-
|
|
75
53
|
# Adding more line noise to format strings will not improve them
|
|
76
54
|
Style/FormatStringToken:
|
|
77
55
|
Enabled: false
|
|
@@ -95,3 +73,187 @@ Style/Lambda:
|
|
|
95
73
|
# Unclear why it's a good idea to give parameters semantically meaningless names
|
|
96
74
|
Style/SingleLineBlockParams:
|
|
97
75
|
Enabled: false
|
|
76
|
+
|
|
77
|
+
############################################################
|
|
78
|
+
# Added in RuboCop 0.80
|
|
79
|
+
|
|
80
|
+
Style/HashEachMethods:
|
|
81
|
+
Enabled: true
|
|
82
|
+
|
|
83
|
+
Style/HashTransformKeys:
|
|
84
|
+
Enabled: true
|
|
85
|
+
|
|
86
|
+
Style/HashTransformValues:
|
|
87
|
+
Enabled: true
|
|
88
|
+
|
|
89
|
+
############################################################
|
|
90
|
+
# Added in RuboCop 0.81
|
|
91
|
+
|
|
92
|
+
Lint/StructNewOverride:
|
|
93
|
+
Enabled: true
|
|
94
|
+
|
|
95
|
+
Lint/RaiseException:
|
|
96
|
+
Enabled: true
|
|
97
|
+
|
|
98
|
+
############################################################
|
|
99
|
+
# Added in RuboCop 0.82
|
|
100
|
+
|
|
101
|
+
Layout/SpaceAroundMethodCallOperator:
|
|
102
|
+
Enabled: true
|
|
103
|
+
|
|
104
|
+
Style/ExponentialNotation:
|
|
105
|
+
Enabled: false
|
|
106
|
+
|
|
107
|
+
############################################################
|
|
108
|
+
# Added in RuboCop 0.83
|
|
109
|
+
|
|
110
|
+
Layout/EmptyLinesAroundAttributeAccessor:
|
|
111
|
+
Enabled: true
|
|
112
|
+
|
|
113
|
+
Style/SlicingWithRange:
|
|
114
|
+
Enabled: true
|
|
115
|
+
|
|
116
|
+
############################################################
|
|
117
|
+
# Added in RuboCop 0.84
|
|
118
|
+
|
|
119
|
+
Lint/DeprecatedOpenSSLConstant:
|
|
120
|
+
Enabled: true
|
|
121
|
+
|
|
122
|
+
############################################################
|
|
123
|
+
# Added in RuboCop 0.85
|
|
124
|
+
|
|
125
|
+
Lint/MixedRegexpCaptureTypes:
|
|
126
|
+
Enabled: true
|
|
127
|
+
|
|
128
|
+
Style/RedundantRegexpEscape:
|
|
129
|
+
Enabled: true
|
|
130
|
+
|
|
131
|
+
Style/RedundantRegexpCharacterClass:
|
|
132
|
+
Enabled: true
|
|
133
|
+
|
|
134
|
+
############################################################
|
|
135
|
+
# Added in Rubocop 0.86
|
|
136
|
+
|
|
137
|
+
Style/RedundantFetchBlock:
|
|
138
|
+
Enabled: true
|
|
139
|
+
|
|
140
|
+
############################################################
|
|
141
|
+
# Added in Rubocop 0.87
|
|
142
|
+
|
|
143
|
+
# Sometimes we separate things for a reason
|
|
144
|
+
Style/AccessorGrouping:
|
|
145
|
+
Enabled: false
|
|
146
|
+
|
|
147
|
+
Style/BisectedAttrAccessor:
|
|
148
|
+
Enabled: true
|
|
149
|
+
|
|
150
|
+
Style/RedundantAssignment:
|
|
151
|
+
Enabled: true
|
|
152
|
+
|
|
153
|
+
############################################################
|
|
154
|
+
# Added in Rubocop 0.88
|
|
155
|
+
|
|
156
|
+
Lint/DuplicateElsifCondition:
|
|
157
|
+
Enabled: true
|
|
158
|
+
|
|
159
|
+
Style/ArrayCoercion:
|
|
160
|
+
Enabled: true
|
|
161
|
+
|
|
162
|
+
Style/CaseLikeIf:
|
|
163
|
+
Enabled: true
|
|
164
|
+
|
|
165
|
+
Style/HashAsLastArrayItem:
|
|
166
|
+
Enabled: true
|
|
167
|
+
|
|
168
|
+
Style/HashLikeCase:
|
|
169
|
+
Enabled: true
|
|
170
|
+
|
|
171
|
+
Style/RedundantFileExtensionInRequire:
|
|
172
|
+
Enabled: true
|
|
173
|
+
|
|
174
|
+
############################################################
|
|
175
|
+
# Added in Rubocop 0.89
|
|
176
|
+
|
|
177
|
+
Lint/BinaryOperatorWithIdenticalOperands:
|
|
178
|
+
Enabled: true
|
|
179
|
+
|
|
180
|
+
Lint/DuplicateRescueException:
|
|
181
|
+
Enabled: true
|
|
182
|
+
|
|
183
|
+
Lint/EmptyConditionalBody:
|
|
184
|
+
Enabled: true
|
|
185
|
+
|
|
186
|
+
Lint/FloatComparison:
|
|
187
|
+
Enabled: true
|
|
188
|
+
|
|
189
|
+
Lint/MissingSuper:
|
|
190
|
+
Enabled: true
|
|
191
|
+
|
|
192
|
+
Lint/OutOfRangeRegexpRef:
|
|
193
|
+
Enabled: true
|
|
194
|
+
|
|
195
|
+
Lint/SelfAssignment:
|
|
196
|
+
Enabled: true
|
|
197
|
+
|
|
198
|
+
Lint/TopLevelReturnWithArgument:
|
|
199
|
+
Enabled: true
|
|
200
|
+
|
|
201
|
+
Lint/UnreachableLoop:
|
|
202
|
+
Enabled: true
|
|
203
|
+
|
|
204
|
+
Style/ExplicitBlockArgument:
|
|
205
|
+
Enabled: true
|
|
206
|
+
|
|
207
|
+
Style/GlobalStdStream:
|
|
208
|
+
Enabled: true
|
|
209
|
+
|
|
210
|
+
Style/OptionalBooleanParameter:
|
|
211
|
+
Enabled: true
|
|
212
|
+
|
|
213
|
+
Style/SingleArgumentDig:
|
|
214
|
+
Enabled: true
|
|
215
|
+
|
|
216
|
+
Style/SoleNestedConditional:
|
|
217
|
+
Enabled: true
|
|
218
|
+
|
|
219
|
+
Style/StringConcatenation:
|
|
220
|
+
Enabled: true
|
|
221
|
+
|
|
222
|
+
############################################################
|
|
223
|
+
# Added in Rubocop 0.90
|
|
224
|
+
|
|
225
|
+
Lint/DuplicateRequire:
|
|
226
|
+
Enabled: true
|
|
227
|
+
|
|
228
|
+
Lint/EmptyFile:
|
|
229
|
+
Enabled: true
|
|
230
|
+
|
|
231
|
+
Lint/TrailingCommaInAttributeDeclaration:
|
|
232
|
+
Enabled: true
|
|
233
|
+
|
|
234
|
+
Lint/UselessMethodDefinition:
|
|
235
|
+
Enabled: true
|
|
236
|
+
|
|
237
|
+
Style/CombinableLoops:
|
|
238
|
+
Enabled: true
|
|
239
|
+
|
|
240
|
+
Style/KeywordParametersOrder:
|
|
241
|
+
Enabled: true
|
|
242
|
+
|
|
243
|
+
Style/RedundantSelfAssignment:
|
|
244
|
+
Enabled: true
|
|
245
|
+
|
|
246
|
+
############################################################
|
|
247
|
+
# Added in Rubocop 0.91
|
|
248
|
+
|
|
249
|
+
Layout/BeginEndAlignment:
|
|
250
|
+
Enabled: true
|
|
251
|
+
|
|
252
|
+
Lint/ConstantDefinitionInBlock:
|
|
253
|
+
Enabled: true
|
|
254
|
+
|
|
255
|
+
Lint/IdentityComparison:
|
|
256
|
+
Enabled: true
|
|
257
|
+
|
|
258
|
+
Lint/UselessTimes:
|
|
259
|
+
Enabled: true
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.7
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## 0.3.1 (next)
|
|
2
|
+
|
|
3
|
+
- Add `values`, `keys`, `each_value`, `each_key`, `find_by_value!`, `find_by_value_str!`
|
|
4
|
+
(PR [#8](https://github.com/dmolesUC/typesafe_enum/pull/8))
|
|
5
|
+
- Set minimum required_ruby_version to 2.7 to reflect 2.6
|
|
6
|
+
[end of life](https://www.ruby-lang.org/en/news/2022/04/12/ruby-2-6-10-released/)
|
|
7
|
+
- Bump .ruby-version to 2.7
|
|
8
|
+
- Bump Rake version to 13 for 2.x/3.x compatibility
|
|
9
|
+
|
|
10
|
+
## 0.3.0 (26 October 2020)
|
|
11
|
+
|
|
12
|
+
- Support explicit nil values
|
|
13
|
+
- Update author email in gemspec
|
|
14
|
+
- Update RuboCop to version 0.91 and pin version
|
|
15
|
+
- Set minimum required_ruby_version to 2.6.0
|
|
16
|
+
- Bump .ruby-version to 2.6.6
|
|
17
|
+
|
|
1
18
|
## 0.2.2 (23 April 2020)
|
|
2
19
|
|
|
3
20
|
- Implement [`Enumerable`](https://ruby-doc.org/core-2.6.5/Enumerable.html)
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TypesafeEnum
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/dmolesUC/typesafe_enum)
|
|
4
4
|
[](https://codeclimate.com/github/dmolesUC/typesafe_enum)
|
|
5
5
|
[](http://inch-ci.org/github/dmolesUC/typesafe_enum)
|
|
6
6
|
[](https://github.com/dmolesUC/typesafe_enum/releases)
|
|
@@ -93,6 +93,20 @@ Scale::KILO.value
|
|
|
93
93
|
# => 1000
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
Even `nil` is a valid value (if set explicitly):
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
class Scheme < TypesafeEnum::Base
|
|
100
|
+
new :HTTP, 'http'
|
|
101
|
+
new :HTTPS, 'https'
|
|
102
|
+
new :EXAMPLE, 'example'
|
|
103
|
+
new :UNKNOWN, nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Scheme::UNKNOWN.value
|
|
107
|
+
# => nil
|
|
108
|
+
```
|
|
109
|
+
|
|
96
110
|
Declaring two instances with the same key will produce an error:
|
|
97
111
|
|
|
98
112
|
```ruby
|
data/Rakefile
CHANGED
|
@@ -1,51 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
require 'rspec/core'
|
|
7
|
-
require 'rspec/core/rake_task'
|
|
8
|
-
|
|
9
|
-
namespace :spec do
|
|
10
|
-
|
|
11
|
-
desc 'Run all unit tests'
|
|
12
|
-
RSpec::Core::RakeTask.new(:unit) do |task|
|
|
13
|
-
task.rspec_opts = %w[--color --format documentation --order default]
|
|
14
|
-
task.pattern = 'unit/**/*_spec.rb'
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
task all: [:unit]
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
desc 'Run all tests'
|
|
21
|
-
task spec: 'spec:all'
|
|
22
|
-
|
|
23
|
-
# ------------------------------------------------------------
|
|
24
|
-
# Coverage
|
|
25
|
-
|
|
26
|
-
desc 'Run all unit tests with coverage'
|
|
27
|
-
task :coverage do
|
|
28
|
-
ENV['COVERAGE'] = 'true'
|
|
29
|
-
Rake::Task['spec:unit'].execute
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# ------------------------------------------------------------
|
|
33
|
-
# RuboCop
|
|
34
|
-
|
|
35
|
-
require 'rubocop/rake_task'
|
|
36
|
-
RuboCop::RakeTask.new
|
|
3
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', __dir__)
|
|
4
|
+
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
|
37
5
|
|
|
38
6
|
# ------------------------------------------------------------
|
|
39
|
-
#
|
|
7
|
+
# Application code
|
|
40
8
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
task.rspec_opts = %w[--color --format documentation --order default]
|
|
44
|
-
task.pattern = 'todo.rb'
|
|
9
|
+
File.expand_path('lib', __dir__).tap do |lib|
|
|
10
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
45
11
|
end
|
|
46
12
|
|
|
47
13
|
# ------------------------------------------------------------
|
|
48
|
-
#
|
|
14
|
+
# Custom tasks
|
|
49
15
|
|
|
50
|
-
desc 'Run
|
|
51
|
-
task default: %i[coverage rubocop]
|
|
16
|
+
desc 'Run tests, check test coverage, check code style, build gem'
|
|
17
|
+
task default: %i[coverage rubocop gem]
|
data/lib/typesafe_enum/base.rb
CHANGED
|
@@ -1,108 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'typesafe_enum/exceptions'
|
|
4
|
+
require 'typesafe_enum/class_methods'
|
|
5
|
+
|
|
3
6
|
# A Ruby implementation of Joshua Bloch's
|
|
4
7
|
# [typesafe enum pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums)
|
|
5
8
|
module TypesafeEnum
|
|
9
|
+
|
|
6
10
|
# Base class for typesafe enum classes.
|
|
7
11
|
class Base
|
|
8
12
|
include Comparable
|
|
9
13
|
|
|
10
14
|
class << self
|
|
11
|
-
include
|
|
12
|
-
|
|
13
|
-
# Returns an array of the enum instances in declaration order
|
|
14
|
-
# @return [Array<self>] All instances of this enum, in declaration order
|
|
15
|
-
def to_a
|
|
16
|
-
as_array.dup
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Returns the number of enum instances
|
|
20
|
-
# @return [Integer] the number of instances
|
|
21
|
-
def size
|
|
22
|
-
as_array.size
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Iterates over the set of enum instances
|
|
26
|
-
# @yield [self] Each instance of this enum, in declaration order
|
|
27
|
-
# @return [Enumerator<self>] All instances of this enum, in declaration order
|
|
28
|
-
def each(&block)
|
|
29
|
-
to_a.each(&block)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Looks up an enum instance based on its key
|
|
33
|
-
# @param key [Symbol] the key to look up
|
|
34
|
-
# @return [self, nil] the corresponding enum instance, or nil
|
|
35
|
-
def find_by_key(key)
|
|
36
|
-
by_key[key]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Looks up an enum instance based on its value
|
|
40
|
-
# @param value [Object] the value to look up
|
|
41
|
-
# @return [self, nil] the corresponding enum instance, or nil
|
|
42
|
-
def find_by_value(value)
|
|
43
|
-
by_value[value]
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Looks up an enum instance based on the string representation of its value
|
|
47
|
-
# @param value_str [String] the string form of the value
|
|
48
|
-
# @return [self, nil] the corresponding enum instance, or nil
|
|
49
|
-
def find_by_value_str(value_str)
|
|
50
|
-
value_str = value_str.to_s
|
|
51
|
-
by_value_str[value_str]
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Looks up an enum instance based on its ordinal
|
|
55
|
-
# @param ord [Integer] the ordinal to look up
|
|
56
|
-
# @return [self, nil] the corresponding enum instance, or nil
|
|
57
|
-
def find_by_ord(ord)
|
|
58
|
-
return nil if ord > size || ord.negative?
|
|
59
|
-
|
|
60
|
-
as_array[ord]
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
def by_key
|
|
66
|
-
@by_key ||= {}
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def by_value
|
|
70
|
-
@by_value ||= {}
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def by_value_str
|
|
74
|
-
@by_value_str ||= {}
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def as_array
|
|
78
|
-
@as_array ||= []
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def valid_key_and_value(instance)
|
|
82
|
-
key = instance.key
|
|
83
|
-
value = instance.value
|
|
84
|
-
if (found = find_by_key(key))
|
|
85
|
-
raise NameError, "#{name}::#{key} already exists" unless value == found.value
|
|
86
|
-
|
|
87
|
-
warn("ignoring redeclaration of #{name}::#{key} with value #{value} (source: #{caller(5..5).first})")
|
|
88
|
-
nil
|
|
89
|
-
else
|
|
90
|
-
raise NameError, "A #{name} instance with value '#{value}' already exists" if find_by_value(value)
|
|
91
|
-
|
|
92
|
-
[key, value]
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def register(instance)
|
|
97
|
-
key, value = valid_key_and_value(instance)
|
|
98
|
-
return unless key && value
|
|
99
|
-
|
|
100
|
-
const_set(key.to_s, instance)
|
|
101
|
-
by_key[key] = instance
|
|
102
|
-
by_value[value] = instance
|
|
103
|
-
by_value_str[value.to_s] = instance
|
|
104
|
-
as_array << instance
|
|
105
|
-
end
|
|
15
|
+
include ClassMethods
|
|
106
16
|
end
|
|
107
17
|
|
|
108
18
|
# The symbol key for the enum instance
|
|
@@ -123,6 +33,9 @@ module TypesafeEnum
|
|
|
123
33
|
# the same enum instance; 1 if this value follows `other`; `nil` if `other`
|
|
124
34
|
# is not an instance of this enum class
|
|
125
35
|
def <=>(other)
|
|
36
|
+
# in the case where the enum being compared is actually the parent
|
|
37
|
+
# class, only `==` will work correctly & we cannot use #is_a? or
|
|
38
|
+
# #instance_of?
|
|
126
39
|
ord <=> other.ord if self.class == other.class
|
|
127
40
|
end
|
|
128
41
|
|
|
@@ -137,17 +50,24 @@ module TypesafeEnum
|
|
|
137
50
|
end
|
|
138
51
|
end
|
|
139
52
|
|
|
53
|
+
# Default implementation includes the enum class, `key`,
|
|
54
|
+
# `ord` and `value`.
|
|
55
|
+
# @return [String] a string representation of the enum instance
|
|
140
56
|
def to_s
|
|
141
|
-
"#{self.class}::#{key} [#{ord}] -> #{value}"
|
|
57
|
+
"#{self.class}::#{key} [#{ord}] -> #{value.inspect}"
|
|
142
58
|
end
|
|
143
59
|
|
|
144
60
|
private
|
|
145
61
|
|
|
146
|
-
|
|
62
|
+
IMPLICIT = Class.new.new
|
|
63
|
+
private_constant :IMPLICIT
|
|
64
|
+
|
|
65
|
+
# TODO: is documentation on this still accurate? does it still need to be private?
|
|
66
|
+
def initialize(key, value = IMPLICIT, &block)
|
|
147
67
|
raise TypeError, "#{key} is not a symbol" unless key.is_a?(Symbol)
|
|
148
68
|
|
|
149
69
|
@key = key
|
|
150
|
-
@value = value
|
|
70
|
+
@value = value == IMPLICIT ? key.to_s.downcase : value
|
|
151
71
|
@ord = self.class.size
|
|
152
72
|
self.class.class_exec(self) do |instance|
|
|
153
73
|
register(instance)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module TypesafeEnum
|
|
2
|
+
# Class methods for {{TypesafeEnum::Base}}.
|
|
3
|
+
module ClassMethods
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
# Returns an array of the enum instances in declaration order
|
|
7
|
+
# @return [Array<self>] All instances of this enum, in declaration order
|
|
8
|
+
def to_a
|
|
9
|
+
as_array.dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returns the number of enum instances
|
|
13
|
+
# @return [Integer] the number of instances
|
|
14
|
+
def size
|
|
15
|
+
as_array.size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Iterates over the set of enum instances
|
|
19
|
+
# @yield [self] Each instance of this enum, in declaration order
|
|
20
|
+
# @return [Enumerator<self>] All instances of this enum, in declaration order
|
|
21
|
+
def each(&block)
|
|
22
|
+
to_a.each(&block)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The set of all keys of all enum instances
|
|
26
|
+
# @return [Enumerator<Symbol>] All keys of all enums, in declaration order
|
|
27
|
+
def keys
|
|
28
|
+
to_a.map(&:key)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The set of all values of all enum instances
|
|
32
|
+
# @return [Enumerator<Object>] All values of all enums, in declaration order
|
|
33
|
+
def values
|
|
34
|
+
to_a.map(&:value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Iterates over the set of keys of all instances (#keys)
|
|
38
|
+
# @yield [Enumerator<Symbol>] Each key of each instance of this enum, in declaration order
|
|
39
|
+
# @return [Enumerator<Symbol>]
|
|
40
|
+
def each_key(&block)
|
|
41
|
+
keys.each(&block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Iterates over the set of values of all instances (#values)
|
|
45
|
+
# @yield [Enumerator<Object>] Each value of each instance of this enum, in declaration order
|
|
46
|
+
# @return [Enumerator<Object>]
|
|
47
|
+
def each_value(&block)
|
|
48
|
+
values.each(&block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Looks up an enum instance based on its key
|
|
52
|
+
# @param key [Symbol] the key to look up
|
|
53
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
|
54
|
+
def find_by_key(key)
|
|
55
|
+
by_key[key]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Looks up an enum instance based on its value
|
|
59
|
+
# @param value [Object] the value to look up
|
|
60
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
|
61
|
+
def find_by_value(value)
|
|
62
|
+
by_value[value]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Looks up an enum instance based on its value
|
|
66
|
+
# @param value [Object] the value to look up
|
|
67
|
+
# @return [self, EnumValidationError] the corresponding enum instance, or throws #EnumValidationError
|
|
68
|
+
def find_by_value!(value)
|
|
69
|
+
valid = find_by_value(value)
|
|
70
|
+
return valid unless valid.nil?
|
|
71
|
+
|
|
72
|
+
raise Exceptions::EnumValidationError, "#{class_name}: #{value} is absurd"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Looks up an enum instance based on the string representation of its value
|
|
76
|
+
# @param value_str [String] the string form of the value
|
|
77
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
|
78
|
+
def find_by_value_str(value_str)
|
|
79
|
+
value_str = value_str.to_s
|
|
80
|
+
by_value_str[value_str]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Looks up an enum instance based on the string representation of its value
|
|
84
|
+
# @param value_str [String] the string form of the value
|
|
85
|
+
# @return [self, EnumValidationError] the corresponding enum instance, or throws #EnumValidationError
|
|
86
|
+
def find_by_value_str!(value_str)
|
|
87
|
+
valid = find_by_value_str(value_str)
|
|
88
|
+
return valid unless valid.nil?
|
|
89
|
+
|
|
90
|
+
raise Exceptions::EnumValidationError, "#{class_name}: #{value_str} is absurd"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Looks up an enum instance based on its ordinal
|
|
94
|
+
# @param ord [Integer] the ordinal to look up
|
|
95
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
|
96
|
+
def find_by_ord(ord)
|
|
97
|
+
return nil if ord > size || ord.negative?
|
|
98
|
+
|
|
99
|
+
as_array[ord]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def by_key
|
|
105
|
+
@by_key ||= {}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def by_value
|
|
109
|
+
@by_value ||= {}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def by_value_str
|
|
113
|
+
@by_value_str ||= {}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def as_array
|
|
117
|
+
@as_array ||= []
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def valid_key_and_value(instance)
|
|
121
|
+
return unless (key = valid_key(instance))
|
|
122
|
+
|
|
123
|
+
[key, valid_value(instance)]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def valid_key(instance)
|
|
127
|
+
key = instance.key
|
|
128
|
+
return key unless (found = find_by_key(key))
|
|
129
|
+
|
|
130
|
+
value = instance.value
|
|
131
|
+
raise NameError, "#{name}::#{key} already exists with value #{found.value.inspect}" unless value == found.value
|
|
132
|
+
|
|
133
|
+
warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def valid_value(instance)
|
|
137
|
+
value = instance.value
|
|
138
|
+
return value unless (found = find_by_value(value))
|
|
139
|
+
|
|
140
|
+
key = instance.key
|
|
141
|
+
raise NameError, "A #{name} instance with value #{value.inspect} already exists: #{found.key}" unless key == found.key
|
|
142
|
+
|
|
143
|
+
# valid_key() should already have warned us, and valid_key_and_value() should have exited early, but just in case
|
|
144
|
+
# :nocov:
|
|
145
|
+
warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
|
|
146
|
+
# :nocov:
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def register(instance)
|
|
150
|
+
key, value = valid_key_and_value(instance)
|
|
151
|
+
return unless key
|
|
152
|
+
|
|
153
|
+
const_set(key.to_s, instance)
|
|
154
|
+
by_key[key] = instance
|
|
155
|
+
by_value[value] = instance
|
|
156
|
+
by_value_str[value.to_s] = instance
|
|
157
|
+
as_array << instance
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns the demodulized class name of the inheriting class
|
|
161
|
+
# @return [String] The demodulized class name
|
|
162
|
+
def class_name
|
|
163
|
+
name.split('::').last
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module TypesafeEnum
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
module ModuleInfo
|
|
5
|
+
# The name of this gem
|
|
6
|
+
NAME = 'typesafe_enum'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
# The version of this gem
|
|
9
|
+
VERSION = '0.3.1'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
# The copyright notice for this gem
|
|
12
|
+
COPYRIGHT = 'Copyright (c) 2022 The Regents of the University of California'
|
|
13
|
+
end
|
|
12
14
|
end
|