typesafe_enum 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e86150c4dbad18dc3b34bc33f36af102c1f6a971de91e1fdd77eda854f00ff88
4
- data.tar.gz: 5e05fae89f1428427afff174ceaf42336e2031851d1428a58b9cf98adef42ba1
3
+ metadata.gz: 45a7fb6aeaf02fa9141a956231233d0cd66206eefd89c36c338bd3b1b5551177
4
+ data.tar.gz: c2cab2eef270ae62222f6f1ae671e7c6b6edfa89831c124ac73aed324c85bcab
5
5
  SHA512:
6
- metadata.gz: 866a544ab2935cd10e40fc44ee9f965ee08f21fcfed13a0412eaccbe09157b93a5a3df067a1c25b2c75d676c7d2dcbcccfe25141de708c77f32230d73cfca93d
7
- data.tar.gz: ba826a728d1e2bed282df2f9d550ea7edd780f45f07c1bcdf6967710de52edd8898ec512281f48d36ba8dfb6d781655e8de7bd5e2adb16473dccf98f8c87416f
6
+ metadata.gz: 12c492eb259e363bfb4f8e03278ff128666b9c4be08d7294bfea47c5986e7e5ecc7f3caa00930a21f436c6aa43a12147f471ad1a2750ebd3a9b1e608c22d07e6
7
+ data.tar.gz: d07316fafdae2ea0d452e9874febc267a64c7c0a84dacad6635c27fa927e1952684e9fd0b40411a580eb91f0325ae2a9f38fd0d5a9ab231cdf0ee0a4e0699393
@@ -0,0 +1,30 @@
1
+ name: Build
2
+ on: [ push, pull_request, workflow_dispatch ]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ os: [ ubuntu-latest, macos-latest ]
9
+ ruby: [ '2.7', '3.0', '3.1' ]
10
+ runs-on: ${{ matrix.os }}
11
+
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v2
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
21
+
22
+ - name: Run checks
23
+ run: bundle exec rake
24
+
25
+ - name: Upload artifacts
26
+ if: ${{ always() }}
27
+ uses: actions/upload-artifact@v3
28
+ with:
29
+ name: artifacts
30
+ path: artifacts/**
data/.gitignore CHANGED
@@ -7,6 +7,9 @@
7
7
  # https://youtrack.jetbrains.com/issue/IDEA-233049
8
8
  .idea/$CACHE_FILE$
9
9
 
10
+ # Build artifacts
11
+ /artifacts/
12
+
10
13
  ############################################################
11
14
  # Generated ignores
12
15
 
@@ -7,6 +7,7 @@
7
7
  <inspection_tool class="GoUnusedVariable" enabled="true" level="WARNING" enabled_by_default="true" />
8
8
  <inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
9
9
  <inspection_tool class="Rubocop" enabled="false" level="WARNING" enabled_by_default="false" />
10
+ <inspection_tool class="RubyMismatchedArgumentType" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
10
11
  <inspection_tool class="RubyStringKeysInHashInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
11
12
  <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
12
13
  <option name="processCode" value="true" />
@@ -1,38 +1,78 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <module type="RUBY_MODULE" version="4">
3
+ <component name="ModuleRunConfigurationManager">
4
+ <shared />
5
+ </component>
3
6
  <component name="NewModuleRootManager" inherit-compiler-output="true">
4
7
  <exclude-output />
5
8
  <content url="file://$MODULE_DIR$" />
6
- <orderEntry type="jdk" jdkName="RVM: ruby-2.6.6" jdkType="RUBY_SDK" />
9
+ <orderEntry type="jdk" jdkName="RVM: ruby-2.7.4" jdkType="RUBY_SDK" />
7
10
  <orderEntry type="sourceFolder" forTests="false" />
8
- <orderEntry type="library" scope="PROVIDED" name="ansi (v1.5.0, RVM: ruby-2.6.6) [gem]" level="application" />
9
- <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.1, RVM: ruby-2.6.6) [gem]" level="application" />
10
- <orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, RVM: ruby-2.6.6) [gem]" level="application" />
11
- <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.3, RVM: ruby-2.6.6) [gem]" level="application" />
12
- <orderEntry type="library" scope="PROVIDED" name="docile (v1.3.2, RVM: ruby-2.6.6) [gem]" level="application" />
13
- <orderEntry type="library" scope="PROVIDED" name="parallel (v1.19.2, RVM: ruby-2.6.6) [gem]" level="application" />
14
- <orderEntry type="library" scope="PROVIDED" name="parser (v2.7.2.0, RVM: ruby-2.6.6) [gem]" level="application" />
15
- <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.0.0, RVM: ruby-2.6.6) [gem]" level="application" />
16
- <orderEntry type="library" scope="PROVIDED" name="rake (v12.3.3, RVM: ruby-2.6.6) [gem]" level="application" />
17
- <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v1.8.2, RVM: ruby-2.6.6) [gem]" level="application" />
18
- <orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.4, RVM: ruby-2.6.6) [gem]" level="application" />
19
- <orderEntry type="library" scope="PROVIDED" name="rspec (v3.9.0, RVM: ruby-2.6.6) [gem]" level="application" />
20
- <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.9.1, RVM: ruby-2.6.6) [gem]" level="application" />
21
- <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.9.1, RVM: ruby-2.6.6) [gem]" level="application" />
22
- <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.9.1, RVM: ruby-2.6.6) [gem]" level="application" />
23
- <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.9.2, RVM: ruby-2.6.6) [gem]" level="application" />
24
- <orderEntry type="library" scope="PROVIDED" name="rubocop (v0.91.0, RVM: ruby-2.6.6) [gem]" level="application" />
25
- <orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v0.8.0, RVM: ruby-2.6.6) [gem]" level="application" />
26
- <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.10.1, RVM: ruby-2.6.6) [gem]" level="application" />
27
- <orderEntry type="library" scope="PROVIDED" name="simplecov (v0.18.5, RVM: ruby-2.6.6) [gem]" level="application" />
28
- <orderEntry type="library" scope="PROVIDED" name="simplecov-console (v0.7.2, RVM: ruby-2.6.6) [gem]" level="application" />
29
- <orderEntry type="library" scope="PROVIDED" name="simplecov-html (v0.12.2, RVM: ruby-2.6.6) [gem]" level="application" />
30
- <orderEntry type="library" scope="PROVIDED" name="terminal-table (v1.8.0, RVM: ruby-2.6.6) [gem]" level="application" />
31
- <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.7.0, RVM: ruby-2.6.6) [gem]" level="application" />
32
- <orderEntry type="library" scope="PROVIDED" name="yard (v0.9.24, RVM: ruby-2.6.6) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="ansi (v1.5.0, RVM: ruby-2.7.4) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="builder (v3.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
14
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.31, RVM: ruby-2.7.4) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="ci_reporter (v2.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="ci_reporter_rspec (v1.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="colorize (v0.8.1, RVM: ruby-2.7.4) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, RVM: ruby-2.7.4) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="docile (v1.4.0, RVM: ruby-2.7.4) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="parallel (v1.22.1, RVM: ruby-2.7.4) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="parser (v3.1.2.1, RVM: ruby-2.7.4) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.4) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.4) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.6.0, RVM: ruby-2.7.4) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.5, RVM: ruby-2.7.4) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rubocop (v0.91.0, RVM: ruby-2.7.4) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v0.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.11.0, RVM: ruby-2.7.4) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="simplecov (v0.21.2, RVM: ruby-2.7.4) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="simplecov-console (v0.9.1, RVM: ruby-2.7.4) [gem]" level="application" />
36
+ <orderEntry type="library" scope="PROVIDED" name="simplecov-html (v0.12.3, RVM: ruby-2.7.4) [gem]" level="application" />
37
+ <orderEntry type="library" scope="PROVIDED" name="simplecov_json_formatter (v0.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="terminal-table (v3.0.2, RVM: ruby-2.7.4) [gem]" level="application" />
39
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
40
+ <orderEntry type="library" scope="PROVIDED" name="webrick (v1.7.0, RVM: ruby-2.7.4) [gem]" level="application" />
41
+ <orderEntry type="library" scope="PROVIDED" name="yard (v0.9.28, RVM: ruby-2.7.4) [gem]" level="application" />
33
42
  </component>
34
43
  <component name="RModuleSettingsStorage">
35
44
  <LOAD_PATH number="2" string0="$MODULE_DIR$/lib" string1="$MODULE_DIR$/spec" />
36
45
  <I18N_FOLDERS number="0" />
37
46
  </component>
47
+ <component name="RakeTasksCache">
48
+ <option name="myRootTask">
49
+ <RakeTaskImpl id="rake">
50
+ <subtasks>
51
+ <RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
52
+ <RakeTaskImpl description="Run tests, check test coverage, check code style, build gem" fullCommand="default" id="default" />
53
+ <RakeTaskImpl description="Build typesafe_enum.gemspec as typesafe_enum-0.3.1.gem" fullCommand="gem" id="gem" />
54
+ <RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
55
+ <RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
56
+ <RakeTaskImpl id="rubocop">
57
+ <subtasks>
58
+ <RakeTaskImpl description="Auto-correct RuboCop offenses" fullCommand="rubocop:auto_correct" id="auto_correct" />
59
+ </subtasks>
60
+ </RakeTaskImpl>
61
+ <RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
62
+ <RakeTaskImpl id="ci">
63
+ <subtasks>
64
+ <RakeTaskImpl id="setup">
65
+ <subtasks>
66
+ <RakeTaskImpl description="" fullCommand="ci:setup:rspec" id="rspec" />
67
+ <RakeTaskImpl description="" fullCommand="ci:setup:rspecbase" id="rspecbase" />
68
+ <RakeTaskImpl description="" fullCommand="ci:setup:rspecdoc" id="rspecdoc" />
69
+ <RakeTaskImpl description="" fullCommand="ci:setup:spec_report_cleanup" id="spec_report_cleanup" />
70
+ </subtasks>
71
+ </RakeTaskImpl>
72
+ </subtasks>
73
+ </RakeTaskImpl>
74
+ </subtasks>
75
+ </RakeTaskImpl>
76
+ </option>
77
+ </component>
38
78
  </module>
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.6
1
+ 2.7
data/CHANGES.md CHANGED
@@ -1,4 +1,13 @@
1
- ## 0.3.0
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)
2
11
 
3
12
  - Support explicit nil values
4
13
  - Update author email in gemspec
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)
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,122 +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
- return unless (key = valid_key(instance))
83
-
84
- [key, valid_value(instance)]
85
- end
86
-
87
- def valid_key(instance)
88
- key = instance.key
89
- return key unless (found = find_by_key(key))
90
-
91
- value = instance.value
92
- raise NameError, "#{name}::#{key} already exists with value #{found.value.inspect}" unless value == found.value
93
-
94
- warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
95
- end
96
-
97
- def valid_value(instance)
98
- value = instance.value
99
- return value unless (found = find_by_value(value))
100
-
101
- key = instance.key
102
- raise NameError, "A #{name} instance with value #{value.inspect} already exists: #{found.key}" unless key == found.key
103
-
104
- # valid_key() should already have warned us, and valid_key_and_value() should have exited early, but just in case
105
- # :nocov:
106
- warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
107
- # :nocov:
108
- end
109
-
110
- def register(instance)
111
- key, value = valid_key_and_value(instance)
112
- return unless key
113
-
114
- const_set(key.to_s, instance)
115
- by_key[key] = instance
116
- by_value[value] = instance
117
- by_value_str[value.to_s] = instance
118
- as_array << instance
119
- end
15
+ include ClassMethods
120
16
  end
121
17
 
122
18
  # The symbol key for the enum instance
@@ -137,6 +33,9 @@ module TypesafeEnum
137
33
  # the same enum instance; 1 if this value follows `other`; `nil` if `other`
138
34
  # is not an instance of this enum class
139
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?
140
39
  ord <=> other.ord if self.class == other.class
141
40
  end
142
41
 
@@ -151,6 +50,9 @@ module TypesafeEnum
151
50
  end
152
51
  end
153
52
 
53
+ # Default implementation includes the enum class, `key`,
54
+ # `ord` and `value`.
55
+ # @return [String] a string representation of the enum instance
154
56
  def to_s
155
57
  "#{self.class}::#{key} [#{ord}] -> #{value.inspect}"
156
58
  end
@@ -160,6 +62,7 @@ module TypesafeEnum
160
62
  IMPLICIT = Class.new.new
161
63
  private_constant :IMPLICIT
162
64
 
65
+ # TODO: is documentation on this still accurate? does it still need to be private?
163
66
  def initialize(key, value = IMPLICIT, &block)
164
67
  raise TypeError, "#{key} is not a symbol" unless key.is_a?(Symbol)
165
68
 
@@ -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.3.0'
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
@@ -0,0 +1,11 @@
1
+ require 'ci/reporter/rake/rspec'
2
+
3
+ # Configure CI::Reporter report generation
4
+ ENV['GENERATE_REPORTS'] ||= 'true'
5
+ ENV['CI_REPORTS'] = 'artifacts/rspec'
6
+
7
+ desc 'Run all specs in spec directory, with coverage'
8
+ task coverage: ['ci:setup:rspec'] do
9
+ ENV['COVERAGE'] ||= 'true'
10
+ Rake::Task[:spec].invoke
11
+ end
data/rakelib/gem.rake ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems/gem_runner'
2
+ require 'typesafe_enum/module_info'
3
+
4
+ gem_root_module = TypesafeEnum
5
+
6
+ class << gem_root_module
7
+ def project_root
8
+ @project_root ||= File.expand_path('..', __dir__)
9
+ end
10
+
11
+ def artifacts_dir
12
+ return project_root unless ENV['CI']
13
+
14
+ @artifacts_dir ||= File.join(project_root, 'artifacts')
15
+ end
16
+
17
+ def gemspec_file
18
+ @gemspec_file ||= begin
19
+ gemspec_files = Dir.glob(File.expand_path('*.gemspec', project_root))
20
+ raise ArgumentError, "Too many .gemspecs: #{gemspec_files.join(', ')}" if gemspec_files.size > 1
21
+ raise ArgumentError, 'No .gemspec file found' if gemspec_files.empty?
22
+
23
+ gemspec_files[0]
24
+ end
25
+ end
26
+
27
+ def gemspec_basename
28
+ File.basename(gemspec_file)
29
+ end
30
+
31
+ def output_file
32
+ @output_file ||= begin
33
+ gem_name = File.basename(gemspec_file, '.*')
34
+ version = self::ModuleInfo::VERSION
35
+ basename = "#{gem_name}-#{version}.gem"
36
+ File.join(artifacts_dir, basename)
37
+ end
38
+ end
39
+
40
+ def output_file_relative
41
+ return File.basename(output_file) unless ENV['CI']
42
+
43
+ @output_file_relative ||= begin
44
+ artifacts_dir_relative = File.basename(artifacts_dir)
45
+ File.join(artifacts_dir_relative, File.basename(output_file))
46
+ end
47
+ end
48
+ end
49
+
50
+ desc "Build #{gem_root_module.gemspec_basename} as #{gem_root_module.output_file_relative}"
51
+ task :gem do
52
+ args = ['build', gem_root_module.gemspec_file, "--output=#{gem_root_module.output_file}"]
53
+ Gem::GemRunner.new.run(args)
54
+ end
@@ -0,0 +1,18 @@
1
+ require 'rubocop'
2
+ require 'rubocop/rake_task'
3
+
4
+ desc 'Run rubocop with HTML output'
5
+ RuboCop::RakeTask.new(:rubocop) do |cop|
6
+ output = ENV['RUBOCOP_OUTPUT'] || 'artifacts/rubocop/index.html'
7
+ puts "Writing RuboCop inspection report to #{output}"
8
+
9
+ cop.verbose = false
10
+ cop.formatters = ['html']
11
+ cop.options = ['--out', output]
12
+ end
13
+
14
+ desc 'Run RuboCop with auto-correct, and output results to console'
15
+ task :ra do
16
+ # b/c we want console output, we can't just use `rubocop:auto_correct`
17
+ RuboCop::CLI.new.run(['--safe-auto-correct'])
18
+ end
data/rakelib/spec.rake ADDED
@@ -0,0 +1,2 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new(:spec)
@@ -36,6 +36,12 @@ class Scheme < TypesafeEnum::Base
36
36
  new :UNKNOWN, nil
37
37
  end
38
38
 
39
+ module Super
40
+ module Modular
41
+ class Enum < TypesafeEnum::Base; end
42
+ end
43
+ end
44
+
39
45
  module TypesafeEnum
40
46
  describe Base do
41
47
 
@@ -139,6 +145,31 @@ module TypesafeEnum
139
145
  it 'is private' do
140
146
  expect { Tarot.new(:PENTACLES) }.to raise_error(NoMethodError)
141
147
  end
148
+
149
+ it 'can be overridden' do
150
+ class ::Extras < Base
151
+ attr_reader :x1
152
+ attr_reader :x2
153
+
154
+ def initialize(key, x1, x2)
155
+ super(key)
156
+ @x1 = x1
157
+ @x2 = x2
158
+ end
159
+
160
+ new(:AB, 'a', 'b') do
161
+ def x3
162
+ 'c'
163
+ end
164
+ end
165
+ end
166
+
167
+ expect(Extras::AB.key).to eq(:AB)
168
+ expect(Extras::AB.value).to eq('ab')
169
+ expect(Extras::AB.x1).to eq('a')
170
+ expect(Extras::AB.x2).to eq('b')
171
+ expect(Extras::AB.x3).to eq('c')
172
+ end
142
173
  end
143
174
 
144
175
  describe :to_a do
@@ -154,13 +185,13 @@ module TypesafeEnum
154
185
  end
155
186
 
156
187
  describe :size do
157
- it 'returns the number of enum instnaces' do
188
+ it 'returns the number of enum instances' do
158
189
  expect(Suit.size).to eq(4)
159
190
  end
160
191
  end
161
192
 
162
193
  describe :count do
163
- it 'returns the number of enum instnaces' do
194
+ it 'returns the number of enum instances' do
164
195
  expect(Suit.count).to eq(4)
165
196
  end
166
197
 
@@ -174,7 +205,7 @@ module TypesafeEnum
174
205
  end
175
206
 
176
207
  describe :each do
177
- it 'iterates the enum values' do
208
+ it 'iterates the enum instances' do
178
209
  expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
179
210
  index = 0
180
211
  Suit.each do |s|
@@ -184,6 +215,42 @@ module TypesafeEnum
184
215
  end
185
216
  end
186
217
 
218
+ describe :keys do
219
+ it 'returns all keys of all instances' do
220
+ expect(Suit.keys).to eq(%i[CLUBS DIAMONDS HEARTS SPADES])
221
+ end
222
+ end
223
+
224
+ describe :values do
225
+ it 'returns all values of all instances' do
226
+ expect(Suit.values).to eq(%w[clubs diamonds hearts spades])
227
+ end
228
+
229
+ end
230
+
231
+ describe :each_key do
232
+ it 'iterates the enum keys' do
233
+ expected = %i[CLUBS DIAMONDS HEARTS SPADES]
234
+ index = 0
235
+ Suit.each_key do |s|
236
+ expect(s).to eq(expected[index])
237
+ index += 1
238
+ end
239
+ end
240
+
241
+ end
242
+
243
+ describe :each_value do
244
+ it 'iterates the enum values' do
245
+ expected = %w[clubs diamonds hearts spades]
246
+ index = 0
247
+ Suit.each_value do |s|
248
+ expect(s).to eq(expected[index])
249
+ index += 1
250
+ end
251
+ end
252
+ end
253
+
187
254
  describe :each_with_index do
188
255
  it 'iterates the enum values with indices' do
189
256
  expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
@@ -424,6 +491,38 @@ module TypesafeEnum
424
491
  end
425
492
  end
426
493
 
494
+ describe :find_by_value! do
495
+ it 'maps values to enum instances' do
496
+ values = %w[clubs diamonds hearts spades]
497
+ expected = Suit.to_a
498
+ values.each_with_index do |n, index|
499
+ expect(Suit.find_by_value!(n)).to be(expected[index])
500
+ end
501
+ end
502
+
503
+ it 'throws EnumValidationError for invalid values' do
504
+ expect { Suit.find_by_value!('wands') }.to raise_error(Exceptions::EnumValidationError)
505
+ end
506
+
507
+ it 'supports enums with symbol values' do
508
+ RGBColor.each do |c|
509
+ expect(RGBColor.find_by_value!(c.value)).to be(c)
510
+ end
511
+ end
512
+
513
+ it 'supports enums with integer values' do
514
+ Scale.each do |s|
515
+ expect(Scale.find_by_value!(s.value)).to be(s)
516
+ end
517
+ end
518
+
519
+ it 'supports enums with explicit nil values' do
520
+ Scheme.each do |s|
521
+ expect(Scheme.find_by_value!(s.value)).to be(s)
522
+ end
523
+ end
524
+ end
525
+
427
526
  describe :find_by_value_str do
428
527
  it 'maps string values to enum instances' do
429
528
  values = %w[clubs diamonds hearts spades]
@@ -451,7 +550,39 @@ module TypesafeEnum
451
550
 
452
551
  it 'supports enums with explicit nil values' do
453
552
  Scheme.each do |s|
454
- expect(Scheme.find_by_value(s.value)).to be(s)
553
+ expect(Scheme.find_by_value_str(s.value)).to be(s)
554
+ end
555
+ end
556
+ end
557
+
558
+ describe :find_by_value_str! do
559
+ it 'maps string values to enum instances' do
560
+ values = %w[clubs diamonds hearts spades]
561
+ expected = Suit.to_a
562
+ values.each_with_index do |n, index|
563
+ expect(Suit.find_by_value_str!(n)).to be(expected[index])
564
+ end
565
+ end
566
+
567
+ it 'throws EnumValidationError for invalid values' do
568
+ expect { Suit.find_by_value_str!('wands') }.to raise_error(Exceptions::EnumValidationError)
569
+ end
570
+
571
+ it 'supports enums with symbol values' do
572
+ RGBColor.each do |c|
573
+ expect(RGBColor.find_by_value_str!(c.value.to_s)).to be(c)
574
+ end
575
+ end
576
+
577
+ it 'supports enums with integer values' do
578
+ Scale.each do |s|
579
+ expect(Scale.find_by_value_str!(s.value.to_s)).to be(s)
580
+ end
581
+ end
582
+
583
+ it 'supports enums with explicit nil values' do
584
+ Scheme.each do |s|
585
+ expect(Scheme.find_by_value_str!(s.value)).to be(s)
455
586
  end
456
587
  end
457
588
  end
@@ -476,6 +607,13 @@ module TypesafeEnum
476
607
  end
477
608
  end
478
609
 
610
+ describe :class_name do
611
+ it 'returns the demodulized class name' do
612
+ expect(Super::Modular::Enum.send(:class_name)).to eq 'Enum'
613
+ expect(Suit.send(:class_name)).to eq 'Suit'
614
+ end
615
+ end
616
+
479
617
  it 'supports case statements' do
480
618
  def pip(suit)
481
619
  case suit
@@ -7,8 +7,8 @@ require 'uri'
7
7
  require 'typesafe_enum/module_info'
8
8
 
9
9
  Gem::Specification.new do |spec|
10
- spec.name = TypesafeEnum::NAME
11
- spec.version = TypesafeEnum::VERSION
10
+ spec.name = TypesafeEnum::ModuleInfo::NAME
11
+ spec.version = TypesafeEnum::ModuleInfo::VERSION
12
12
  spec.authors = ['David Moles']
13
13
  spec.email = ['dmoles@berkeley.edu']
14
14
  spec.summary = 'Typesafe enum pattern for Ruby'
@@ -23,9 +23,11 @@ Gem::Specification.new do |spec|
23
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
24
  spec.require_paths = ['lib']
25
25
 
26
- spec.required_ruby_version = '>= 2.6.0'
26
+ spec.required_ruby_version = '>= 2.7'
27
27
 
28
- spec.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3'
28
+ spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
29
+ spec.add_development_dependency 'colorize', '~> 0.8'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
29
31
  spec.add_development_dependency 'rspec', '~> 3.9'
30
32
  spec.add_development_dependency 'rubocop', '0.91'
31
33
  spec.add_development_dependency 'simplecov', '~> 0.18'
metadata CHANGED
@@ -1,35 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typesafe_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Moles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-26 00:00:00.000000000 Z
11
+ date: 2022-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: ci_reporter_rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '12.3'
20
- - - ">="
19
+ version: '1.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
21
32
  - !ruby/object:Gem::Version
22
- version: 12.3.3
33
+ version: '0.8'
23
34
  type: :development
24
35
  prerelease: false
25
36
  version_requirements: !ruby/object:Gem::Requirement
26
37
  requirements:
27
38
  - - "~>"
28
39
  - !ruby/object:Gem::Version
29
- version: '12.3'
30
- - - ">="
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
31
53
  - !ruby/object:Gem::Version
32
- version: 12.3.3
54
+ version: '13.0'
33
55
  - !ruby/object:Gem::Dependency
34
56
  name: rspec
35
57
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +135,7 @@ executables: []
113
135
  extensions: []
114
136
  extra_rdoc_files: []
115
137
  files:
138
+ - ".github/workflows/build.yml"
116
139
  - ".gitignore"
117
140
  - ".idea/.rakeTasks"
118
141
  - ".idea/codeStyles/Project.xml"
@@ -134,7 +157,13 @@ files:
134
157
  - Rakefile
135
158
  - lib/typesafe_enum.rb
136
159
  - lib/typesafe_enum/base.rb
160
+ - lib/typesafe_enum/class_methods.rb
161
+ - lib/typesafe_enum/exceptions.rb
137
162
  - lib/typesafe_enum/module_info.rb
163
+ - rakelib/coverage.rake
164
+ - rakelib/gem.rake
165
+ - rakelib/rubocop.rake
166
+ - rakelib/spec.rake
138
167
  - spec/.rubocop.yml
139
168
  - spec/spec_helper.rb
140
169
  - spec/unit/typesafe_enum/base_spec.rb
@@ -151,14 +180,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
180
  requirements:
152
181
  - - ">="
153
182
  - !ruby/object:Gem::Version
154
- version: 2.6.0
183
+ version: '2.7'
155
184
  required_rubygems_version: !ruby/object:Gem::Requirement
156
185
  requirements:
157
186
  - - ">="
158
187
  - !ruby/object:Gem::Version
159
188
  version: '0'
160
189
  requirements: []
161
- rubygems_version: 3.0.8
190
+ rubygems_version: 3.1.6
162
191
  signing_key:
163
192
  specification_version: 4
164
193
  summary: Typesafe enum pattern for Ruby