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 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
@@ -0,0 +1,7 @@
1
+ if defined?(Motion::Project::Config)
2
+ Motion::Project::App.setup do |app|
3
+ app.files << File.join(File.dirname(__FILE__), 'enum.rb')
4
+ end
5
+ else
6
+ require File.join(File.dirname(__FILE__), 'enum.rb')
7
+ end
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,3 @@
1
+ module EinsteinEnum
2
+ Version = '1.0.0'
3
+ end
@@ -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
+
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