typesafe_enum 0.1.0

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