typesafe_enum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e6e6d0a15330f5ec2dd13061e2da603ec354705
4
+ data.tar.gz: cfaca6a2bc5a2dd50dd2442abc23a6fe5775e8dd
5
+ SHA512:
6
+ metadata.gz: 0808cf4b53fc63cd69d330cec45fe2124f73380070e6db5d7c6c03e389f8dbf32d1b9c36ea54147cbe6f2abfcc458b35416371f1242c17d191208e91342033ad
7
+ data.tar.gz: 604dfd70e95d123a72045757aba249b474c3cb8869282d4eac3d38dd37663a56e5f7724439de1dafc20532b8d2eb79bb813fff60fad22064ac5229dd050df7c7
data/.gitignore ADDED
@@ -0,0 +1,42 @@
1
+ # Ruby defaults
2
+
3
+ /.bundle/
4
+ /.yardoc
5
+ /Gemfile.lock
6
+ /_yardoc/
7
+ /coverage/
8
+ /doc/
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
12
+ *.bundle
13
+ *.so
14
+ *.o
15
+ *.a
16
+ mkmf.log
17
+
18
+ # Database
19
+
20
+ db/*.sqlite3
21
+
22
+ # Logs
23
+
24
+ /log/
25
+
26
+ # IntellJ
27
+
28
+ *.iml
29
+ *.ipr
30
+ *.iws
31
+ *.ids
32
+ .rakeTasks
33
+
34
+ # Emacs
35
+
36
+ *~
37
+ \#*
38
+ .#*
39
+
40
+ # Mac OS
41
+
42
+ .DS_Store
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ # Disable compact style check for example.rb
2
+ Style/ClassAndModuleChildren:
3
+ Exclude:
4
+ - 'example.rb'
5
+
6
+ # Disable line-length check; it's too easy for the cure to be worse than the disease
7
+ Metrics/LineLength:
8
+ Enabled: False
9
+
10
+ # Disable problematic module documentation check (see https://github.com/bbatsov/rubocop/issues/947)
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ # Allow one line around class body (Style/EmptyLines will still disallow two or more)
15
+ Style/EmptyLinesAroundClassBody:
16
+ Enabled: false
17
+
18
+ # Allow one line around module body (Style/EmptyLines will still disallow two or more)
19
+ Style/EmptyLinesAroundModuleBody:
20
+ Enabled: false
21
+
22
+ # Allow one line around block body (Style/EmptyLines will still disallow two or more)
23
+ Style/EmptyLinesAroundBlockBody:
24
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/CHANGES.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 The Regents of the University of California
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # typesafe_enum
2
+
3
+ [![Build Status](https://travis-ci.org/dmolesUC3/typesafe_enum.png?branch=master)](https://travis-ci.org/dmolesUC3/typesafe_enum)
4
+ [![Code Climate](https://codeclimate.com/github/dmolesUC3/typesafe_enum.png)](https://codeclimate.com/github/dmolesUC3/typesafe_enum)
5
+ [![Inline docs](http://inch-ci.org/github/dmolesUC3/typesafe_enum.png)](http://inch-ci.org/github/dmolesUC3/typesafe_enum)
6
+ [![Gem Version](https://img.shields.io/gem/v/typesafe_enum.svg)](https://github.com/dmolesUC3/typesafe_enum/releases)
7
+
8
+ A Ruby implementation of Joshua Bloch's
9
+ [typesafe enum pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums),
10
+ with syntax loosely inspired by [Ruby::Enum](https://github.com/dblock/ruby-enum).
11
+
12
+ ## Basic usage
13
+
14
+ Create a new enum class and a set of instances:
15
+
16
+ ```ruby
17
+ require 'typesafe_enum'
18
+
19
+ class Suit < TypesafeEnum::Base
20
+ new :CLUBS
21
+ new :DIAMONDS
22
+ new :HEARTS
23
+ new :SPADES
24
+ end
25
+ ```
26
+
27
+ A constant is declared for each instance, with the key symbol as its
28
+ value:
29
+
30
+ ```ruby
31
+ Suit::CLUBS
32
+ # => #<Suit:0x007fe9b3ba2698 @key=:CLUBS, @value="clubs", @ord=0>
33
+ ```
34
+
35
+ By default, the `value` of an instance is its `key` symbol, lowercased:
36
+
37
+ ```ruby
38
+ Suit::CLUBS.key
39
+ # => :CLUBS
40
+ Suit::CLUBS.value
41
+ # => 'clubs'
42
+ ```
43
+
44
+ But you can also declare an explicit `value`:
45
+
46
+ ```ruby
47
+ class Tarot < TypesafeEnum::Base
48
+ new :CUPS, 'Cups'
49
+ new :COINS, 'Coins'
50
+ new :WANDS, 'Wands'
51
+ new :SWORDS, 'Swords'
52
+ end
53
+
54
+ Tarot::CUPS.value
55
+ # => 'Cups'
56
+ ```
57
+
58
+ And `values` need not be strings:
59
+
60
+ ```ruby
61
+ class Scale < TypesafeEnum::Base
62
+ new :DECA, 10
63
+ new :HECTO, 100
64
+ new :KILO, 1_000
65
+ new :MEGA, 1_000_000
66
+ end
67
+
68
+ Scale::KILO.value
69
+ # => 1000
70
+ ```
71
+
72
+ ## Ordering
73
+
74
+ Enum instances have an ordinal value corresponding to their declaration
75
+ order:
76
+
77
+ ```ruby
78
+ Suit::SPADES.ord
79
+ # => 3
80
+ ```
81
+
82
+ And enum instances are comparable (within a type) based on that order:
83
+
84
+ ```ruby
85
+ Suit::SPADES.is_a?(Comparable)
86
+ # => true
87
+ Suit::SPADES > Suit::DIAMONDS
88
+ # => true
89
+ Suit::SPADES > Tarot::CUPS
90
+ # ArgumentError: comparison of Suit with Tarot failed
91
+ ```
92
+
93
+ ## Convenience methods on enum classes
94
+
95
+ ### `::to_a`
96
+
97
+ Returns an array of the enum instances in declaration order:
98
+
99
+ ```ruby
100
+ Tarot.to_a
101
+ # => [#<Tarot:0x007fd4db30eca8 @key=:CUPS, @value="Cups", @ord=0>, #<Tarot:0x007fd4db30ebe0 @key=:COINS, @value="Coins", @ord=1>, #<Tarot:0x007fd4db30eaf0 @key=:WANDS, @value="Wands", @ord=2>, #<Tarot:0x007fd4db30e9b0 @key=:SWORDS, @value="Swords", @ord=3>]
102
+ ```
103
+
104
+ ### `::size`
105
+
106
+ Returns the number of enum instances:
107
+
108
+ ```ruby
109
+ Suit.size
110
+ # => 4
111
+ ```
112
+
113
+ ### `::each`, `::each_with_index`, and `::map`
114
+
115
+ Iterate over the set of enum instances:
116
+
117
+ ```ruby
118
+ Suit.each { |s| puts s.value }
119
+ # clubs
120
+ # diamonds
121
+ # hearts
122
+ # spades
123
+
124
+ Suit.each_with_index { |s, i| puts "#{i}: #{s.key}" }
125
+ # 0: CLUBS
126
+ # 1: DIAMONDS
127
+ # 2: HEARTS
128
+ # 3: SPADES
129
+
130
+ Suit.map(&:value)
131
+ # => ["clubs", "diamonds", "hearts", "spades"]
132
+ ```
133
+
134
+ ### `::find_by_key`, `::find_by_value`, `::find_by_ord`
135
+
136
+ Look up an enum instance based on its key, value, or ordinal:
137
+
138
+ ```ruby
139
+ Tarot.find_by_key(:CUPS)
140
+ # => #<Tarot:0x007faab19fda40 @key=:CUPS, @value="Cups", @ord=0>
141
+ Tarot.find_by_value('Wands')
142
+ # => #<Tarot:0x007faab19fd8b0 @key=:WANDS, @value="Wands", @ord=2>
143
+ Tarot.find_by_ord(3)
144
+ # => #<Tarot:0x007faab19fd810 @key=:SWORDS, @value="Swords", @ord=3>
145
+ ```
146
+
147
+ ## Enum classes with methods
148
+
149
+ Enum classes can have methods, and other non-enum constants:
150
+
151
+ ```ruby
152
+ class Suit < TypesafeEnum::Base
153
+ new :CLUBS
154
+ new :DIAMONDS
155
+ new :HEARTS
156
+ new :SPADES
157
+
158
+ ALL_PIPS = %w(♣ ♦ ♥ ♠)
159
+
160
+ def pip
161
+ ALL_PIPS[self.ord]
162
+ end
163
+ end
164
+
165
+ Suit::ALL_PIPS
166
+ # => ["♣", "♦", "♥", "♠"]
167
+
168
+ Suit::CLUBS.pip
169
+ # => "♣"
170
+
171
+ Suit.map(&:pip)
172
+ # => ["♣", "♦", "♥", "♠"]
173
+ ```
174
+
175
+ ## Enum instances with methods
176
+
177
+ Enum instances can declare their own methods via `instance_eval`:
178
+
179
+ ```ruby
180
+ class Operation < TypesafeEnum::Base
181
+ new(:PLUS, '+').instance_eval do
182
+ def eval(x, y)
183
+ x + y
184
+ end
185
+ end
186
+ new(:MINUS, '-').instance_eval do
187
+ def eval(x, y)
188
+ x - y
189
+ end
190
+ end
191
+ end
192
+
193
+ Operation::PLUS.eval(11, 17)
194
+ # => 28
195
+
196
+ Operation::MINUS.eval(28, 11)
197
+ # => 17
198
+
199
+ Operation.map { |op| op.eval(39, 23) }
200
+ # => [62, 16]
201
+ ```
202
+
203
+ ## How is this different from [Ruby::Enum](https://github.com/dblock/ruby-enum)?
204
+
205
+ [Ruby::Enum](https://github.com/dblock/ruby-enum) is much closer to the classic
206
+ [C enumeration](https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#Enumerations)
207
+ as seen in C, [C++](https://msdn.microsoft.com/en-us/library/2dzy4k6e.aspx),
208
+ [C#](https://msdn.microsoft.com/en-us/library/sbbt4032.aspx), and
209
+ [Objective-C](https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW6).
210
+ In C and most C-like languages, an `enum` is simply a valued set of `int` values
211
+ (though C++ and others require an explicit cast to assign an `enum` value to
212
+ an `int` variable).
213
+
214
+ Similarly, a `Ruby::Enum` class is simply a valued set of values of any type,
215
+ with convenience methods for iterating over the set. Usually the values are
216
+ strings, but they can be of any type.
217
+
218
+ ```ruby
219
+ # String enum
220
+ class Foo
221
+ include Ruby::Enum
222
+
223
+ new :BAR, 'bar'
224
+ new :BAZ, 'baz'
225
+ end
226
+
227
+ Foo::BAR
228
+ # => "bar"
229
+ Foo::BAR == 'bar'
230
+ # => true
231
+
232
+ # Integer enum
233
+ class Bar
234
+ include Ruby::Enum
235
+
236
+ new :BAR, 1
237
+ new :BAZ, 2
238
+ end
239
+
240
+ Bar::BAR
241
+ # => "bar"
242
+ Bar::BAR == 1
243
+ # => true
244
+ ```
245
+
246
+ Java introduced the concept of "typesafe enums", first as a
247
+ [design pattern]((http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums))
248
+ and later as a
249
+ [first-class language construct](https://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html).
250
+ In Java, an `Enum` class news a closed, valued set of _instances of that class,_ rather than
251
+ of a primitive type such as an `int`, and those instances have all the features of other objects,
252
+ such as methods, fields, and type membership. Likewise, a `TypesafeEnum` class news a valued set
253
+ of instances of that class, rather than of a set of some other type.
254
+
255
+ ```ruby
256
+ Suit::CLUBS.is_a?(Suit)
257
+ # => true
258
+ Tarot::CUPS == 'Cups'
259
+ # => false
260
+ ```
261
+
262
+ ## How is this different from [java.lang.Enum](http://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html)?
263
+
264
+ *[TODO: details]*
265
+
266
+ ### Clunkier syntax
267
+
268
+ ### No special `switch`/`case` support
269
+
270
+ ### No serialization support
271
+
272
+ - but `#==`, `#hash` etc. are `Marshal`-safe
273
+
274
+ ### No support classes like [`EnumSet`](http://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html) and
275
+ [`EnumMap`](http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html)
276
+
277
+ ### Enum classes are not closed
278
+
279
+ It's Ruby, so even though `:new` is private to each enum class, you
280
+ can always work around that:
281
+
282
+ ```ruby
283
+ Suit.send(:new, :JOKERS)
284
+ # => #<Suit:0x007fc9e44e4778 @key=:JOKERS, @value="jokers", @ord=4>
285
+ Suit.map(&:key)
286
+ # => [:CLUBS, :DIAMONDS, :HEARTS, :SPADES, :JOKERS]
287
+ Suit.size
288
+ # => 5
289
+ ```
290
+
291
+ ### *[Other limitations...]*
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # ------------------------------------------------------------
2
+ # RSpec
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ namespace :spec do
8
+
9
+ desc 'Run all unit tests'
10
+ RSpec::Core::RakeTask.new(:unit) do |task|
11
+ task.rspec_opts = %w(--color --format documentation --order default)
12
+ task.pattern = 'unit/**/*_spec.rb'
13
+ end
14
+
15
+ task all: [:unit]
16
+ end
17
+
18
+ desc 'Run all tests'
19
+ task spec: 'spec:all'
20
+
21
+ # ------------------------------------------------------------
22
+ # Coverage
23
+
24
+ desc 'Run all unit tests with coverage'
25
+ task :coverage do
26
+ ENV['COVERAGE'] = 'true'
27
+ Rake::Task['spec:unit'].execute
28
+ end
29
+
30
+ # ------------------------------------------------------------
31
+ # RuboCop
32
+
33
+ require 'rubocop/rake_task'
34
+ RuboCop::RakeTask.new
35
+
36
+ # ------------------------------------------------------------
37
+ # TODOs
38
+
39
+ desc 'List TODOs (from spec/todo.rb)'
40
+ RSpec::Core::RakeTask.new(:todo) do |task|
41
+ task.rspec_opts = %w(--color --format documentation --order default)
42
+ task.pattern = 'todo.rb'
43
+ end
44
+
45
+ # ------------------------------------------------------------
46
+ # Defaults
47
+
48
+ desc 'Run unit tests, check test coverage, run acceptance tests, check code style'
49
+ task default: [:coverage, :rubocop]
@@ -0,0 +1,133 @@
1
+ # A Ruby implementation of Joshua Bloch's
2
+ # [typesafe enum pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums)
3
+ module TypesafeEnum
4
+ # Base class for typesafe enum classes.
5
+ class Base
6
+ include Comparable
7
+
8
+ class << self
9
+
10
+ # Returns an array of the enum instances in declaration order
11
+ def to_a
12
+ as_array.dup
13
+ end
14
+
15
+ # Returns the number of enum instances
16
+ def size
17
+ as_array ? as_array.length : 0
18
+ end
19
+
20
+ # Iterates over the set of enum instances
21
+ def each(&block)
22
+ to_a.each(&block)
23
+ end
24
+
25
+ # Iterates over the set of enum instances
26
+ def each_with_index(&block)
27
+ to_a.each_with_index(&block)
28
+ end
29
+
30
+ # Iterates over the set of enum instances
31
+ def map(&block)
32
+ to_a.map(&block)
33
+ end
34
+
35
+ # Looks up an enum instance based on its key
36
+ def find_by_key(key)
37
+ by_key[key]
38
+ end
39
+
40
+ # Looks up an enum instance based on its value
41
+ def find_by_value(value)
42
+ by_value[value]
43
+ end
44
+
45
+ # Looks up an enum instance based on its ordinal
46
+ def find_by_ord(ord)
47
+ return nil if ord < 0 || ord > size
48
+ as_array[ord]
49
+ end
50
+
51
+ private
52
+
53
+ attr_accessor :by_key
54
+ attr_accessor :by_value
55
+ attr_accessor :as_array
56
+
57
+ def undefine_class
58
+ enclosing_module = Module.nesting.last
59
+ class_value = name.split('::').last || ''
60
+ enclosing_module.send(:remove_const, class_value)
61
+ end
62
+
63
+ def register(instance)
64
+ ensure_registries
65
+ key, value = valid_key_and_value(instance)
66
+
67
+ by_key[key] = instance
68
+ by_value[value] = instance
69
+ as_array << instance
70
+ const_set(key.to_s, instance)
71
+ end
72
+
73
+ def ensure_registries
74
+ self.by_key ||= {}
75
+ self.by_value ||= {}
76
+ self.as_array ||= []
77
+ end
78
+
79
+ def valid_key_and_value(instance)
80
+ key = instance.key
81
+ value = instance.value
82
+
83
+ begin
84
+ fail NameError, "#{name}::#{key} already exists" if find_by_key(key)
85
+ fail NameError, "A #{name} instance with value '#{value}' already exists" if find_by_value(value)
86
+ rescue NameError => e
87
+ undefine_class
88
+ raise e
89
+ end
90
+
91
+ [key, value]
92
+ end
93
+
94
+ end
95
+
96
+ # The symbol key for the enum instance
97
+ attr_reader :key
98
+ # The value encapsulated by the enum instance
99
+ attr_reader :value
100
+ # The ordinal of the enum instance, in declaration order
101
+ attr_reader :ord
102
+
103
+ # Compares two instances of the same enum class based on their declaration order
104
+ def <=>(other)
105
+ ord <=> other.ord if self.class == other.class
106
+ end
107
+
108
+ # Generates a Fixnum hash value for this enum instance
109
+ def hash
110
+ @hash ||= begin
111
+ result = 17
112
+ result = 31 * result + self.class.hash
113
+ result = 31 * result + ord
114
+ result.is_a?(Fixnum) ? result : result.hash
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def initialize(key, value = nil)
121
+ fail TypeError, "#{key} is not a symbol" unless key.is_a?(Symbol)
122
+ @key = key
123
+ @value = value || key.to_s.downcase
124
+ @ord = self.class.size
125
+ self.class.class_exec(self) do |instance|
126
+ register(instance)
127
+ end
128
+ end
129
+
130
+ private_class_method :new
131
+
132
+ end
133
+ end
@@ -0,0 +1,10 @@
1
+ module TypesafeEnum
2
+ # The name of this gem
3
+ NAME = 'typesafe_enum'
4
+
5
+ # The version of this gem
6
+ VERSION = '0.1.0'
7
+
8
+ # The copyright notice for this gem
9
+ COPYRIGHT = 'Copyright (c) 2015 The Regents of the University of California'
10
+ end
@@ -0,0 +1,3 @@
1
+ module TypesafeEnum
2
+ Dir.glob(File.expand_path('../typesafe_enum/*.rb', __FILE__)).sort.each(&method(:require))
3
+ end
data/spec/.rubocop.yml ADDED
@@ -0,0 +1,7 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ Metrics/MethodLength:
4
+ Enabled: false
5
+
6
+ Metrics/ModuleLength:
7
+ Enabled: false
@@ -0,0 +1,31 @@
1
+ # ------------------------------------------------------------
2
+ # SimpleCov setup
3
+
4
+ if ENV['COVERAGE']
5
+ require 'simplecov'
6
+ require 'simplecov-console'
7
+
8
+ SimpleCov.minimum_coverage 100
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
12
+ SimpleCov::Formatter::HTMLFormatter,
13
+ SimpleCov::Formatter::Console,
14
+ ]
15
+ end
16
+ end
17
+
18
+ # ------------------------------------------------------------
19
+ # Rspec configuration
20
+
21
+ require 'rspec'
22
+
23
+ RSpec.configure do |config|
24
+ config.raise_errors_for_deprecations!
25
+ config.mock_with :rspec
26
+ end
27
+
28
+ # ------------------------------------------------------------
29
+ # TypesafeEnum
30
+
31
+ require 'typesafe_enum'
@@ -0,0 +1,370 @@
1
+ # coding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ class Suit < TypesafeEnum::Base
5
+ new :CLUBS
6
+ new :DIAMONDS
7
+ new :HEARTS
8
+ new :SPADES
9
+ end
10
+
11
+ class Tarot < TypesafeEnum::Base
12
+ new :CUPS, 'Cups'
13
+ new :COINS, 'Coins'
14
+ new :WANDS, 'Wands'
15
+ new :SWORDS, 'Swords'
16
+ end
17
+
18
+ module TypesafeEnum
19
+ describe Base do
20
+
21
+ describe ':: new' do
22
+ it ' news a constant enum value' do
23
+ enum = Suit::CLUBS
24
+ expect(enum).to be_a(Suit)
25
+ end
26
+
27
+ it 'insists symbols be symbols' do
28
+ expect do
29
+ class Cheat < Base
30
+ new 'spades', 'spades'
31
+ end
32
+ end.to raise_error(TypeError)
33
+ end
34
+
35
+ it 'insists symbols be uppercase' do
36
+ expect do
37
+ class Cheat < Base
38
+ new :spades, 'spades'
39
+ end
40
+ end.to raise_error(NameError)
41
+ end
42
+
43
+ it 'disallows duplicate symbols' do
44
+ expect do
45
+ class Cheat < Base
46
+ new :SPADES, 'spades'
47
+ new :SPADES, 'more spades'
48
+ end
49
+ end.to raise_error(NameError)
50
+
51
+ expect { Cheat.class }.to raise_error(NameError)
52
+ end
53
+
54
+ it 'disallows duplicate values' do
55
+ expect do
56
+ class Cheat < Base
57
+ new :SPADES, 'spades'
58
+ new :ALSO_SPADES, 'spades'
59
+ end
60
+ end.to raise_error(NameError)
61
+
62
+ expect { Cheat.class }.to raise_error(NameError)
63
+ end
64
+
65
+ it 'defaults the value to a lower-cased version of the symbol' do
66
+ expect(Suit::CLUBS.value).to eq('clubs')
67
+ expect(Suit::DIAMONDS.value).to eq('diamonds')
68
+ expect(Suit::HEARTS.value).to eq('hearts')
69
+ expect(Suit::SPADES.value).to eq('spades')
70
+ end
71
+
72
+ it 'is private' do
73
+ expect { Tarot.new(:PENTACLES) }.to raise_error(NoMethodError)
74
+ end
75
+ end
76
+
77
+ describe '::to_a' do
78
+ it 'returns the values as an array' do
79
+ expect(Suit.to_a).to eq([Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES])
80
+ end
81
+ it 'returns a copy' do
82
+ array = Suit.to_a
83
+ array.clear
84
+ expect(array.empty?).to eq(true)
85
+ expect(Suit.to_a).to eq([Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES])
86
+ end
87
+ end
88
+
89
+ describe '::size' do
90
+ it 'returns the number of enum instnaces' do
91
+ expect(Suit.size).to eq(4)
92
+ end
93
+ end
94
+
95
+ describe '::each' do
96
+ it 'iterates the enum values' do
97
+ expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
98
+ index = 0
99
+ Suit.each do |s|
100
+ expect(s).to be(expected[index])
101
+ index += 1
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '::each_with_index' do
107
+ it 'iterates the enum values with indices' do
108
+ expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
109
+ Suit.each_with_index do |s, index|
110
+ expect(s).to be(expected[index])
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '::map' do
116
+ it 'maps enum values' do
117
+ all_keys = Suit.map(&:key)
118
+ expect(all_keys).to eq([:CLUBS, :DIAMONDS, :HEARTS, :SPADES])
119
+ end
120
+ end
121
+
122
+ describe '#<=>' do
123
+ it 'orders enum instances' do
124
+ Suit.each_with_index do |s1, i1|
125
+ Suit.each_with_index do |s2, i2|
126
+ expect(s1 <=> s2).to eq(i1 <=> i2)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#==' do
133
+ it 'returns true for identical instances, false otherwise' do
134
+ Suit.each do |s1|
135
+ Suit.each do |s2|
136
+ identical = s1.equal?(s2)
137
+ expect(s1 == s2).to eq(identical)
138
+ end
139
+ end
140
+ end
141
+
142
+ it 'reports instances as unequal to nil and other objects' do
143
+ a_nil_value = nil
144
+ Suit.each do |s|
145
+ expect(s.nil?).to eq(false)
146
+ expect(s == a_nil_value).to eq(false)
147
+ Tarot.each do |t|
148
+ expect(s == t).to eq(false)
149
+ expect(t == s).to eq(false)
150
+ end
151
+ end
152
+ end
153
+
154
+ it 'survives marshalling' do
155
+ Suit.each do |s1|
156
+ dump = Marshal.dump(s1)
157
+ s2 = Marshal.load(dump)
158
+ expect(s1 == s2).to eq(true)
159
+ expect(s2 == s1).to eq(true)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#!=' do
165
+ it 'returns false for identical instances, true otherwise' do
166
+ Suit.each do |s1|
167
+ Suit.each do |s2|
168
+ different = !s1.equal?(s2)
169
+ expect(s1 != s2).to eq(different)
170
+ end
171
+ end
172
+ end
173
+
174
+ it 'reports instances as unequal to nil and other objects' do
175
+ a_nil_value = nil
176
+ Suit.each do |s|
177
+ expect(s != a_nil_value).to eq(true)
178
+ Tarot.each do |t|
179
+ expect(s != t).to eq(true)
180
+ expect(t != s).to eq(true)
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ describe '#hash' do
187
+ it 'gives consistent values' do
188
+ Suit.each do |s1|
189
+ Suit.each do |s2|
190
+ expect(s1.hash == s2.hash).to eq(s1 == s2)
191
+ end
192
+ end
193
+ end
194
+
195
+ it 'returns different values for different types' do
196
+ class Suit2 < Base
197
+ new :CLUBS, 'Clubs'
198
+ new :DIAMONDS, 'Diamonds'
199
+ new :HEARTS, 'Hearts'
200
+ new :SPADES, 'Spades'
201
+ end
202
+
203
+ Suit.each do |s1|
204
+ s2 = Suit2.find_by_key(s1.key)
205
+ expect(s1.hash == s2.hash).to eq(false)
206
+ end
207
+ end
208
+
209
+ it 'survives marshalling' do
210
+ Suit.each do |s1|
211
+ dump = Marshal.dump(s1)
212
+ s2 = Marshal.load(dump)
213
+ expect(s2.hash).to eq(s1.hash)
214
+ end
215
+ end
216
+
217
+ it 'always returns a Fixnum' do
218
+ Suit.each do |s1|
219
+ expect(s1.hash).to be_a(Fixnum)
220
+ end
221
+ end
222
+ end
223
+
224
+ describe '#eql?' do
225
+ it 'is consistent with #hash' do
226
+ Suit.each do |s1|
227
+ Suit.each do |s2|
228
+ expect(s1.eql?(s2)).to eq(s1.hash == s2.hash)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ describe '#value' do
235
+ it 'returns the string value of the enum instance' do
236
+ expected = %w(clubs diamonds hearts spades)
237
+ Suit.each_with_index do |s, index|
238
+ expect(s.value).to eq(expected[index])
239
+ end
240
+ end
241
+ end
242
+
243
+ describe '#key' do
244
+ it 'returns the symbol key of the enum instance' do
245
+ expected = [:CLUBS, :DIAMONDS, :HEARTS, :SPADES]
246
+ Suit.each_with_index do |s, index|
247
+ expect(s.key).to eq(expected[index])
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '#ord' do
253
+ it 'returns the ord value of the enum instance' do
254
+ Suit.each_with_index do |s, index|
255
+ expect(s.ord).to eq(index)
256
+ end
257
+ end
258
+ end
259
+
260
+ describe '::find_by_key' do
261
+ it 'maps symbol keys to enum instances' do
262
+ keys = [:CLUBS, :DIAMONDS, :HEARTS, :SPADES]
263
+ expected = Suit.to_a
264
+ keys.each_with_index do |k, index|
265
+ expect(Suit.find_by_key(k)).to be(expected[index])
266
+ end
267
+ end
268
+
269
+ it 'returns nil for invalid keys' do
270
+ expect(Suit.find_by_key(:WANDS)).to be_nil
271
+ end
272
+ end
273
+
274
+ describe '::find_by_value' do
275
+ it 'maps values to enum instances' do
276
+ values = %w(clubs diamonds hearts spades)
277
+ expected = Suit.to_a
278
+ values.each_with_index do |n, index|
279
+ expect(Suit.find_by_value(n)).to be(expected[index])
280
+ end
281
+ end
282
+
283
+ it 'returns nil for invalid values' do
284
+ expect(Suit.find_by_value('wands')).to be_nil
285
+ end
286
+
287
+ it 'supports enums with symbol values' do
288
+ class RGBColors < Base
289
+ new :RED, :red
290
+ new :GREEN, :green
291
+ new :BLUE, :blue
292
+ end
293
+
294
+ RGBColors.each do |c|
295
+ expect(RGBColors.find_by_value(c.value)).to be(c)
296
+ end
297
+ end
298
+
299
+ it 'supports enums with integer values' do
300
+ class Scale < Base
301
+ new :DECA, 10
302
+ new :HECTO, 100
303
+ new :KILO, 1_000
304
+ new :MEGA, 1_000_000
305
+ end
306
+
307
+ Scale.each do |s|
308
+ expect(Scale.find_by_value(s.value)).to be(s)
309
+ end
310
+ end
311
+ end
312
+
313
+ describe '::find_by_ord' do
314
+
315
+ it 'maps ordinal indices to enum instances' do
316
+ Suit.each do |s|
317
+ expect(Suit.find_by_ord(s.ord)).to be(s)
318
+ end
319
+ end
320
+
321
+ it 'returns nil for negative indices' do
322
+ expect(Suit.find_by_ord(-1)).to be_nil
323
+ end
324
+
325
+ it 'returns nil for out-of-range indices' do
326
+ expect(Suit.find_by_ord(Suit.size)).to be_nil
327
+ end
328
+
329
+ it 'returns nil for invalid indices' do
330
+ expect(Suit.find_by_ord(100)).to be_nil
331
+ end
332
+ end
333
+
334
+ it 'supports case statements' do
335
+ def pip(suit)
336
+ case suit
337
+ when Suit::CLUBS
338
+ '♣'
339
+ when Suit::DIAMONDS
340
+ '♦'
341
+ when Suit::HEARTS
342
+ '♥'
343
+ when Suit::SPADES
344
+ '♠'
345
+ else
346
+ fail "unknown suit: #{self}"
347
+ end
348
+ end
349
+
350
+ expect(Suit.map { |s| pip(s) }).to eq(%w(♣ ♦ ♥ ♠))
351
+ end
352
+
353
+ it 'supports "inner class" methods via instance_eval' do
354
+ class Operation < Base
355
+ new(:PLUS, '+').instance_eval do
356
+ def eval(x, y)
357
+ x + y
358
+ end
359
+ end
360
+ new(:MINUS, '-').instance_eval do
361
+ def eval(x, y)
362
+ x - y
363
+ end
364
+ end
365
+ end
366
+
367
+ expect(Operation.map { |op| op.eval(39, 23) }).to eq([39 + 23, 39 - 23])
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'uri'
6
+ require 'typesafe_enum/module_info'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = TypesafeEnum::NAME
10
+ spec.version = TypesafeEnum::VERSION
11
+ spec.authors = ['David Moles']
12
+ spec.email = ['david.moles@ucop.edu']
13
+ spec.summary = 'Typesafe enum pattern for Ruby'
14
+ spec.description = 'A gem that implements the typesafe enum pattern in Ruby'
15
+ spec.license = 'MIT'
16
+
17
+ origin_uri = URI(`git config --get remote.origin.url`.chomp)
18
+ spec.homepage = URI::HTTP.build(host: origin_uri.host, path: origin_uri.path.chomp('.git')).to_s
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.7'
27
+ spec.add_development_dependency 'rake', '~> 10.4'
28
+ spec.add_development_dependency 'rspec', '~> 3.2'
29
+ spec.add_development_dependency 'simplecov', '~> 0.9.2'
30
+ spec.add_development_dependency 'simplecov-console', '~> 0.2.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.32.1'
32
+ spec.add_development_dependency 'yard', '~> 0.8'
33
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typesafe_enum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Moles
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov-console
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.2.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.32.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.32.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.8'
111
+ description: A gem that implements the typesafe enum pattern in Ruby
112
+ email:
113
+ - david.moles@ucop.edu
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rubocop.yml"
120
+ - ".ruby-version"
121
+ - ".travis.yml"
122
+ - ".yardopts"
123
+ - CHANGES.md
124
+ - Gemfile
125
+ - LICENSE.md
126
+ - README.md
127
+ - Rakefile
128
+ - lib/typesafe_enum.rb
129
+ - lib/typesafe_enum/base.rb
130
+ - lib/typesafe_enum/module_info.rb
131
+ - spec/.rubocop.yml
132
+ - spec/spec_helper.rb
133
+ - spec/unit/typesafe_enum/base_spec.rb
134
+ - typesafe_enum.gemspec
135
+ homepage: http://github.com/dmolesUC3/typesafe_enum
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.4.5.1
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Typesafe enum pattern for Ruby
159
+ test_files:
160
+ - spec/.rubocop.yml
161
+ - spec/spec_helper.rb
162
+ - spec/unit/typesafe_enum/base_spec.rb
163
+ has_rdoc: