einstein-enum 1.0.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 +7 -0
- data/README.md +96 -0
- data/lib/einstein-enum.rb +7 -0
- data/lib/enum.rb +167 -0
- data/lib/version.rb +3 -0
- data/spec/einstein_enum_spec.rb +163 -0
- data/spec/spec_helper.rb +0 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f8a9f734114e08c89f7b162fc923d5be76db5fb9
|
4
|
+
data.tar.gz: ae6349c335b99e8ac11e0dd13d0fc020412f9e3e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 225faebfed556dc7165fe79b86e90ef09c54454dc74710f3f5dbbe9373fa2d1b8289b9b8897669840dd9b28e0eaa2e78347dec3a0815d2b49b46abf7189ee494
|
7
|
+
data.tar.gz: 397d849c770e61564d8f83d092af2b61fdf6e5ee635e54134d4a1b24a651a6d62651a3d2e9fcd94f716e668acfd2b2ac9ccd41bd118ff96b14cd9b2da5ed45f7
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Einstein-enum
|
2
|
+
|
3
|
+
This is a port of Swift's Enum type. In Swift, Enums have all sorts of great
|
4
|
+
features, like associated values and raw values. GENIUS.
|
5
|
+
|
6
|
+
They are truly awesome, unlike generics, terrible compilation error messages,
|
7
|
+
and the rather pedantic `init` method requirements.
|
8
|
+
|
9
|
+
|
10
|
+
# Compatibility
|
11
|
+
|
12
|
+
I wrote this for use in RubyMotion, but I wrote it in a way that it is
|
13
|
+
compatible with all Ruby implementations (that I know of!).
|
14
|
+
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class Api < Enum
|
20
|
+
value :Status
|
21
|
+
value :Posts, raw_value: :posts
|
22
|
+
# these represent an endpoint for "user detail", either by id or username
|
23
|
+
value :User, Fixnum
|
24
|
+
value :User, String
|
25
|
+
|
26
|
+
def url
|
27
|
+
# inside this method, the constants "Status" and methods "User" are defined
|
28
|
+
# to return the appropriate enum values/matchers
|
29
|
+
case self
|
30
|
+
when Status
|
31
|
+
"/status"
|
32
|
+
when User(-1) # one off values can be matched!
|
33
|
+
"/users/by_role/admin"
|
34
|
+
when User(Fixnum)
|
35
|
+
# you can access the values using array access
|
36
|
+
id = self[0]
|
37
|
+
"/users/by_id/#{id}"
|
38
|
+
when User(String)
|
39
|
+
username = self[0]
|
40
|
+
"/users/by_name/#{username}"
|
41
|
+
when Posts
|
42
|
+
"/posts"
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
status_endpoint = Api.Status
|
52
|
+
user_endpoint = Api.User(2) # if you tried `Api.User` you would get a 'value not defined' expection
|
53
|
+
|
54
|
+
|
55
|
+
# matching is really where it's at:
|
56
|
+
case status_endpoint
|
57
|
+
# simple enum values
|
58
|
+
when Api.Status
|
59
|
+
when Api.Posts
|
60
|
+
# here is the cool "polymorphic" matching. I have not seen any other Ruby Enum
|
61
|
+
# gems offer this:
|
62
|
+
when Api.User(Fixnum)
|
63
|
+
when Api.User(String)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# the ability to have methods associated with the enum values is very handy, and
|
68
|
+
# adds object-oriented ideas to the boring old 'enum' type.
|
69
|
+
puts status_endpoint.url # => https://api.api.com/api/v1/status
|
70
|
+
```
|
71
|
+
|
72
|
+
## Testing
|
73
|
+
|
74
|
+
```
|
75
|
+
rake spec
|
76
|
+
```
|
77
|
+
|
78
|
+
## Todo
|
79
|
+
|
80
|
+
It would be neat to have "named" values, instead of just positional.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Api < Enum
|
84
|
+
value :User, id: Fixnum
|
85
|
+
end
|
86
|
+
|
87
|
+
user_endpoint = Api.User(id: 2)
|
88
|
+
|
89
|
+
id = user_endpoint[:id]
|
90
|
+
id = user_endpoint.id
|
91
|
+
```
|
92
|
+
|
93
|
+
Using Einstein-enum, I'd like to build a [Moya][]-like tool for RubyMotion
|
94
|
+
|
95
|
+
|
96
|
+
[Moya]: https://github.com/ashfurrow/Moya
|
data/lib/enum.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
module EinsteinEnum
|
2
|
+
|
3
|
+
class Enum
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def value(name, *types)
|
8
|
+
if self == Enum
|
9
|
+
raise 'Don\'t add values to Enum'
|
10
|
+
end
|
11
|
+
|
12
|
+
if !name.is_a?(Symbol)
|
13
|
+
raise '`name` must be a symbol'
|
14
|
+
end
|
15
|
+
|
16
|
+
opts = {}
|
17
|
+
if types.last.is_a?(Hash)
|
18
|
+
opts = types.pop
|
19
|
+
end
|
20
|
+
|
21
|
+
new_value = EnumValue.new
|
22
|
+
new_value.enum = self
|
23
|
+
new_value.name = name
|
24
|
+
new_value.types = types
|
25
|
+
new_value.raw_value = opts[:raw_value] || (2 ** all_values.count)
|
26
|
+
|
27
|
+
if existing_value = find(name, types)
|
28
|
+
raise "There is already a value (#{existing_value}) that matches #{new_value}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if all_values.any? { |existing_value| existing_value.raw_value == new_value.raw_value }
|
32
|
+
raise "There is already a value (#{existing_value}) with the raw_value of `#{new_value.raw_value.inspect}`"
|
33
|
+
end
|
34
|
+
|
35
|
+
all_values << new_value
|
36
|
+
|
37
|
+
if !self.respond_to?(name)
|
38
|
+
define_singleton_method(name) do |*type_values|
|
39
|
+
create(name, *type_values)
|
40
|
+
end
|
41
|
+
if types.count == 0
|
42
|
+
self.const_set(name, new_value)
|
43
|
+
end
|
44
|
+
define_method(name) do |*type_values|
|
45
|
+
self.class.create(name, *type_values)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
new_value
|
50
|
+
end
|
51
|
+
|
52
|
+
def all_values
|
53
|
+
if self == Enum
|
54
|
+
return []
|
55
|
+
end
|
56
|
+
|
57
|
+
@all_values ||= [] + self.superclass.all_values
|
58
|
+
end
|
59
|
+
|
60
|
+
def find(name, type_values)
|
61
|
+
all_values.find do |enum_value|
|
62
|
+
enum_value.name == name && types_match(enum_value.types, type_values)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def types_match(types, type_values)
|
67
|
+
if types.count != type_values.count
|
68
|
+
false
|
69
|
+
else
|
70
|
+
matches = true
|
71
|
+
if type_values.all? { |t| t.is_a?(Class) }
|
72
|
+
types.each_with_index do |t, index|
|
73
|
+
matches &&= type_values[index].ancestors.include?(t)
|
74
|
+
break if not matches
|
75
|
+
end
|
76
|
+
else
|
77
|
+
types.each_with_index do |t, index|
|
78
|
+
matches &&= type_values[index].is_a?(t)
|
79
|
+
break if not matches
|
80
|
+
end
|
81
|
+
end
|
82
|
+
matches
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def create(name, *type_values)
|
87
|
+
enum_value = find(name, type_values)
|
88
|
+
if enum_value.nil?
|
89
|
+
raise "could not find value for #{name}(#{type_values.map{|t|t.to_s}.join(', ')})"
|
90
|
+
elsif !type_values.empty? && type_values.all? { |t| t.is_a?(Class) }
|
91
|
+
return enum_value
|
92
|
+
else
|
93
|
+
return enum_value.instance(type_values)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_accessor :enum_value
|
100
|
+
attr_accessor :values
|
101
|
+
|
102
|
+
def ===(value)
|
103
|
+
if value.is_a?(EnumValue)
|
104
|
+
value.raw_value == raw_value
|
105
|
+
elsif value.is_a?(Enum)
|
106
|
+
value.enum_value.name == enum_value.name && value.values.count == values.count && begin
|
107
|
+
matches = true
|
108
|
+
values.each_with_index do |v, index|
|
109
|
+
matches &&= v == value.values[index]
|
110
|
+
break if not matches
|
111
|
+
end
|
112
|
+
matches
|
113
|
+
end
|
114
|
+
else
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def raw_value
|
120
|
+
enum_value.raw_value
|
121
|
+
end
|
122
|
+
|
123
|
+
def [](index)
|
124
|
+
values[index]
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
"#{enum_value.name}(#{values.map{|t|t.to_s}.join(', ')})"
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
class EnumValue
|
135
|
+
attr_accessor :enum
|
136
|
+
attr_accessor :name
|
137
|
+
attr_accessor :types
|
138
|
+
attr_accessor :raw_value
|
139
|
+
|
140
|
+
def ===(value)
|
141
|
+
if value.is_a?(EnumValue)
|
142
|
+
value.raw_value == raw_value
|
143
|
+
elsif value.is_a?(Enum)
|
144
|
+
value.enum_value.name == name && Enum.types_match(types, value.values)
|
145
|
+
else
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def instance(values)
|
151
|
+
instance = enum.new
|
152
|
+
instance.enum_value = self
|
153
|
+
instance.values = values
|
154
|
+
instance
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
"#{name}(#{types.map{|t|t.to_s}.join(', ')})"
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
if !defined?(::Enum)
|
166
|
+
Enum = EinsteinEnum::Enum
|
167
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require "einstein_enum"
|
2
|
+
|
3
|
+
|
4
|
+
ROOT = "https://api.api.com/api/v1"
|
5
|
+
|
6
|
+
|
7
|
+
class Api < Enum
|
8
|
+
value :Status
|
9
|
+
value :User, Fixnum
|
10
|
+
value :User, String
|
11
|
+
value :Posts, raw_value: :posts
|
12
|
+
|
13
|
+
def root
|
14
|
+
ROOT
|
15
|
+
end
|
16
|
+
|
17
|
+
def url
|
18
|
+
case self
|
19
|
+
when Status
|
20
|
+
root + "/status"
|
21
|
+
when User(Fixnum)
|
22
|
+
id = self[0]
|
23
|
+
root + "/users/by_id/#{id}"
|
24
|
+
when User(String)
|
25
|
+
username = self[0]
|
26
|
+
root + "/users/by_name/#{username}"
|
27
|
+
when Posts
|
28
|
+
root + "/posts"
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# extending an Enum
|
38
|
+
class AnotherApi < Api
|
39
|
+
value :PostDetail, Fixnum
|
40
|
+
|
41
|
+
def url
|
42
|
+
case self
|
43
|
+
when PostDetail(Fixnum)
|
44
|
+
root + "/posts/#{self[0]}"
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
status = Api.Status
|
54
|
+
user_type = Api.User(Fixnum)
|
55
|
+
user_id = Api.User(2)
|
56
|
+
user_name = Api.User('colinta')
|
57
|
+
posts = Api.Posts
|
58
|
+
post_detail = AnotherApi.PostDetail(2)
|
59
|
+
|
60
|
+
|
61
|
+
RSpec.describe EinsteinEnum do
|
62
|
+
|
63
|
+
describe "matches" do
|
64
|
+
it "should match Api.Status" do
|
65
|
+
expect(case status
|
66
|
+
when Api.Status
|
67
|
+
true
|
68
|
+
when Api.User(Fixnum)
|
69
|
+
false
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end).to(eq(true))
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should match Api.Status" do
|
76
|
+
expect(case status
|
77
|
+
when Api.Status
|
78
|
+
true
|
79
|
+
when Api.User(Fixnum)
|
80
|
+
false
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end).to eq(true)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should match Api.User(Fixnum)" do
|
87
|
+
expect(case user_id
|
88
|
+
when Api.Status
|
89
|
+
false
|
90
|
+
when Api.User(String)
|
91
|
+
false
|
92
|
+
when Api.User(Fixnum)
|
93
|
+
true
|
94
|
+
else
|
95
|
+
false
|
96
|
+
end).to eq(true)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should match Api.User(String)" do
|
100
|
+
expect(case user_name
|
101
|
+
when Api.Status
|
102
|
+
false
|
103
|
+
when Api.User(Fixnum)
|
104
|
+
false
|
105
|
+
when Api.User(String)
|
106
|
+
true
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end).to eq(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should match Api.User(2)" do
|
113
|
+
expected_id = 2
|
114
|
+
expect(case user_id
|
115
|
+
when Api.Status
|
116
|
+
false
|
117
|
+
when Api.User(expected_id - 1)
|
118
|
+
false
|
119
|
+
when Api.User(expected_id)
|
120
|
+
true
|
121
|
+
else
|
122
|
+
false
|
123
|
+
end).to eq(true)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "raw values" do
|
128
|
+
it "should be unique" do
|
129
|
+
expect(user_type.raw_value).not_to eq(user_type.raw_value)
|
130
|
+
expect(user_id.raw_value).not_to eq(user_id.raw_value)
|
131
|
+
expect(user_name.raw_value).not_to eq(user_name.raw_value)
|
132
|
+
expect(posts.raw_value).not_to eq(posts.raw_value)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should be customizable" do
|
136
|
+
expect(posts.raw_value).to eq(:posts)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe("methods") do
|
141
|
+
it "should create the Status url string" do
|
142
|
+
expect(status.url).to eq(ROOT + "/status")
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should create the User(Fixnum) url string" do
|
146
|
+
expect(user_id.url).to eq(ROOT + "/users/by_id/2")
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should create the User(String) url string" do
|
150
|
+
expect(user_name.url).to eq(ROOT + "/users/by_name/colinta")
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should create the Post url string" do
|
154
|
+
expect(posts.url).to eq(ROOT + "/posts")
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should create the PostDetail(Fixnum) url string" do
|
158
|
+
expect(post_detail.url).to eq(ROOT + "/posts/2")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
data/spec/spec_helper.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: einstein-enum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin T.A. Gray
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.11'
|
27
|
+
description: |
|
28
|
+
Let's not kid ourselves: Swift didn't remove much of the tedium of iOS
|
29
|
+
development. However, it introduced the best ENUM type that I've ever seen.
|
30
|
+
email:
|
31
|
+
- colinta@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- README.md
|
37
|
+
- lib/einstein-enum.rb
|
38
|
+
- lib/enum.rb
|
39
|
+
- lib/version.rb
|
40
|
+
- spec/einstein_enum_spec.rb
|
41
|
+
- spec/spec_helper.rb
|
42
|
+
homepage: https://github.com/colinta/einstein-enum
|
43
|
+
licenses:
|
44
|
+
- BSD
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.4.5
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Swift's Enums are genius. Especially in a language that doesn't suck.
|
66
|
+
test_files:
|
67
|
+
- spec/einstein_enum_spec.rb
|
68
|
+
- spec/spec_helper.rb
|