typesafe_enum 0.2.2 → 0.3.1

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