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.
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.6.5
1
+ 2.7
data/.travis.yml CHANGED
@@ -1,2 +1,9 @@
1
1
  language: ruby
2
2
 
3
+ os:
4
+ - linux
5
+ - osx
6
+
7
+ rvm:
8
+ - 2.6
9
+ - 2.7
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
- [![Build Status](https://travis-ci.org/dmolesUC/typesafe_enum.svg?branch=master)](https://travis-ci.org/dmolesUC/typesafe_enum)
3
+ [![Build Status](https://github.com/dmolesUC/typesafe_enum/actions/workflows/build.yml/badge.svg?branch=master)](https://travis-ci.org/dmolesUC/typesafe_enum)
4
4
  [![Code Climate](https://codeclimate.com/github/dmolesUC/typesafe_enum.svg)](https://codeclimate.com/github/dmolesUC/typesafe_enum)
5
5
  [![Inline docs](http://inch-ci.org/github/dmolesUC/typesafe_enum.svg)](http://inch-ci.org/github/dmolesUC/typesafe_enum)
6
6
  [![Gem Version](https://img.shields.io/gem/v/typesafe_enum.svg)](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
- # RSpec
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
- # TODOs
7
+ # Application code
40
8
 
41
- desc 'List TODOs (from spec/todo.rb)'
42
- RSpec::Core::RakeTask.new(:todo) do |task|
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
- # Defaults
14
+ # Custom tasks
49
15
 
50
- desc 'Run unit tests, check test coverage, run acceptance tests, check code style'
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]
@@ -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 Enumerable
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
- def initialize(key, value = nil, &block)
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 || key.to_s.downcase
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
@@ -0,0 +1,7 @@
1
+ module TypesafeEnum
2
+ # Grouping module for exceptions
3
+ module Exceptions
4
+ # Base class of enum validation exceptions
5
+ class EnumValidationError < StandardError; end
6
+ end
7
+ end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypesafeEnum
4
- # The name of this gem
5
- NAME = 'typesafe_enum'
4
+ module ModuleInfo
5
+ # The name of this gem
6
+ NAME = 'typesafe_enum'
6
7
 
7
- # The version of this gem
8
- VERSION = '0.2.2'
8
+ # The version of this gem
9
+ VERSION = '0.3.1'
9
10
 
10
- # The copyright notice for this gem
11
- COPYRIGHT = 'Copyright (c) 2020 The Regents of the University of California'
11
+ # The copyright notice for this gem
12
+ COPYRIGHT = 'Copyright (c) 2022 The Regents of the University of California'
13
+ end
12
14
  end